]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 8.2.0988: getting directory contents is always case sorted v8.2.0988
authorBram Moolenaar <Bram@vim.org>
Tue, 16 Jun 2020 18:03:43 +0000 (20:03 +0200)
committerBram Moolenaar <Bram@vim.org>
Tue, 16 Jun 2020 18:03:43 +0000 (20:03 +0200)
Problem:    Getting directory contents is always case sorted.
Solution:   Add sort options and v:collate. (Christian Brabandt, closes #6229)

17 files changed:
runtime/doc/eval.txt
runtime/doc/mlang.txt
src/auto/configure
src/cmdexpand.c
src/config.h.in
src/configure.ac
src/evalfunc.c
src/evalvars.c
src/ex_cmds2.c
src/fileio.c
src/filepath.c
src/globals.h
src/proto/fileio.pro
src/testdir/test_cmdline.vim
src/testdir/test_functions.vim
src/version.c
src/vim.h

index 816aeca143bf201164cd095bdd49cc999612e50b..4ab8bbd1ee03e05997d656dd82d55667ae9310b5 100644 (file)
@@ -1745,6 +1745,14 @@ v:cmdbang        Set like v:cmdarg for a file read/write command.  When a "!"
                was used the value is 1, otherwise it is 0.  Note that this
                can only be used in autocommands.  For user commands |<bang>|
                can be used.
+                                               *v:collate* *collate-variable*
+v:collate      The current locale setting for collation order of the runtime
+               environment.  This allows Vim scripts to be aware of the
+               current locale encoding.  Technical: it's the value of
+               LC_COLLATE.  When not using a locale the value is "C".
+               This variable can not be set directly, use the |:language|
+               command.
+               See |multi-lang|.
 
                                *v:completed_item* *completed_item-variable*
 v:completed_item
@@ -2683,8 +2691,10 @@ pyxeval({expr})                  any     evaluate |python_x| expression
 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}
+readdir({dir} [, {expr} [, {dict}]])
+                               List    file names in {dir} selected by {expr}
+readdirex({dir} [, {expr} [, {dict}]])
+                               List    file info in {dir} selected by {expr}
 readfile({fname} [, {type} [, {max}]])
                                List    get list of lines from file {fname}
 reduce({object}, {func} [, {initial}])
@@ -7904,11 +7914,12 @@ rand([{expr}])                                          *rand()* *random*
                        :echo rand(seed)
                        :echo rand(seed) % 16  " random number 0 - 15
 <
-readdir({directory} [, {expr}])                                *readdir()*
+readdir({directory} [, {expr} [, {dict}]])                     *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).
+               The list will be sorted (case sensitive), see the {dict}
+               argument below for changing the sort order.
 
                When {expr} is omitted all entries are included.
                When {expr} is given, it is evaluated to check what to do:
@@ -7926,18 +7937,38 @@ readdir({directory} [, {expr}])                         *readdir()*
 <              To skip hidden and backup files: >
                  readdir(dirname, {n -> n !~ '^\.\|\~$'})
 
+<              The optional {dict} argument allows for further custom
+               values. Currently this is used to specify if and how sorting
+               should be performed. The dict can have the following members:
+
+                   sort    How to sort the result returned from the system.
+                           Valid values are:
+                               "none"      do not sort (fastest method)
+                               "case"      sort case sensitive (byte value of
+                                           each character, technically, using
+                                           strcmp()) (default)
+                               "icase"     sort case insensitive (technically
+                                           using strcasecmp())
+                               "collate"   sort using the collation order
+                                           of the "POSIX" or "C" |locale|
+                                           (technically using strcoll())
+                           Other values are silently ignored.
+
+               For example, to get a list of all files in the current
+               directory without sorting the individual entries: >
+                 readdir('.', '1', #{sort: 'none'})
 <              If you want to get a directory tree: >
-                  function! s:tree(dir)
-                      return {a:dir : map(readdir(a:dir),
+                 function! s:tree(dir)
+                     return {a:dir : map(readdir(a:dir),
                      \ {_, x -> isdirectory(x) ?
-                     \          {x : s:tree(a:dir . '/' . x)} : x})}
-                  endfunction
-                  echo s:tree(".")
+                     \          {x : s:tree(a:dir . '/' . x)} : x})}
+                 endfunction
+                 echo s:tree(".")
 <
                Can also be used as a |method|: >
                        GetDirName()->readdir()
 <
-readdirex({directory} [, {expr}])                      *readdirex()*
+readdirex({directory} [, {expr} [, {dict}]])                   *readdirex()*
                Extended version of |readdir()|.
                Return a list of Dictionaries with file and directory
                information in {directory}.
@@ -7946,7 +7977,9 @@ readdirex({directory} [, {expr}])                 *readdirex()*
                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 list will by default be sorted by name (case sensitive),
+               the sorting can be changed by using the optional {dict}
+               argument, see |readdir()|.
 
                The Dictionary for file and directory information has the
                following items:
@@ -7986,6 +8019,11 @@ readdirex({directory} [, {expr}])                        *readdirex()*
                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$'})
+<
+               For example, to get a list of all files in the current
+               directory without sorting the individual entries: >
+                 readdirex(dirname, '1', #{sort: 'none'})
+
 <
                Can also be used as a |method|: >
                        GetDirName()->readdirex()
index 629907c1624385a86dae5401f196578475e45905..b453a39e31d55ff6d52d9efe9b0c3522bd1852aa 100644 (file)
@@ -37,6 +37,7 @@ use of "-" and "_".
 :lan[guage] mes[sages]
 :lan[guage] cty[pe]
 :lan[guage] tim[e]
+:lan[guage] col[late]
                        Print the current language (aka locale).
                        With the "messages" argument the language used for
                        messages is printed.  Technical: LC_MESSAGES.
@@ -44,15 +45,19 @@ use of "-" and "_".
                        character encoding is printed.  Technical: LC_CTYPE.
                        With the "time" argument the language used for
                        strftime() is printed.  Technical: LC_TIME.
+                       With the "collate" argument the language used for
+                       collation order is printed.  Technical: LC_COLLATE.
                        Without argument all parts of the locale are printed
                        (this is system dependent).
                        The current language can also be obtained with the
-                       |v:lang|, |v:ctype| and |v:lc_time| variables.
+                       |v:lang|, |v:ctype|, |v:collate| and |v:lc_time|
+                       variables.
 
 :lan[guage] {name}
 :lan[guage] mes[sages] {name}
 :lan[guage] cty[pe] {name}
 :lan[guage] tim[e] {name}
+:lan[guage] col[late] {name}
                        Set the current language (aka locale) to {name}.
                        The locale {name} must be a valid locale on your
                        system.  Some systems accept aliases like "en" or
@@ -72,7 +77,10 @@ use of "-" and "_".
                        With the "time" argument the language used for time
                        and date messages is set.  This affects strftime().
                        This sets $LC_TIME.
-                       Without an argument both are set, and additionally
+                       With the "collate" argument the language used for the
+                       collation order is set.  This affects sorting of
+                       characters. This sets $LC_COLLATE.
+                       Without an argument all are set, and additionally
                        $LANG is set.
                        When compiled with the |+float| feature the LC_NUMERIC
                        value will always be set to "C", so that floating
index 4c3ff325d048bfea9b45f70a0906a364b58604f6..f642a0b5f9b762c2b8aefc45f0a62760a602dbbc 100755 (executable)
@@ -12618,7 +12618,7 @@ for ac_func in fchdir fchown fchmod fsync getcwd getpseudotty \
        getpwent getpwnam getpwuid getrlimit gettimeofday localtime_r lstat \
        memset mkdtemp nanosleep opendir putenv qsort readlink select setenv \
        getpgid setpgid setsid sigaltstack sigstack sigset sigsetjmp sigaction \
-       sigprocmask sigvec strcasecmp strerror strftime stricmp strncasecmp \
+       sigprocmask sigvec strcasecmp strcoll strerror strftime stricmp strncasecmp \
        strnicmp strpbrk strptime strtol tgetent towlower towupper iswupper \
        tzset usleep utime utimes mblen ftruncate unsetenv posix_openpt
 do :
index b0ea057ae1e7f912d2c575b3ae9711574d6f2fd9..9508d669dd48eb70b892297fdb020a288043ab40 100644 (file)
@@ -1728,7 +1728,8 @@ set_one_cmd_context(
            {
                if ( STRNCMP(arg, "messages", p - arg) == 0
                  || STRNCMP(arg, "ctype", p - arg) == 0
-                 || STRNCMP(arg, "time", p - arg) == 0)
+                 || STRNCMP(arg, "time", p - arg) == 0
+                 || STRNCMP(arg, "collate", p - arg) == 0)
                {
                    xp->xp_context = EXPAND_LOCALES;
                    xp->xp_pattern = skipwhite(p);
index 7055b6f005f50bf25c35bdfcb29bbea7e89c8534..b11448021f87a5939902fb92b97445d96dfa892c 100644 (file)
 #undef HAVE_SIGVEC
 #undef HAVE_SMACK
 #undef HAVE_STRCASECMP
+#undef HAVE_STRCOLL
 #undef HAVE_STRERROR
 #undef HAVE_STRFTIME
 #undef HAVE_STRICMP
index dccf3dce85941bfd43151861becc69d52938bbe2..05439188208e28d10d1151fdcf1802af7db28696 100644 (file)
@@ -3739,7 +3739,7 @@ AC_CHECK_FUNCS(fchdir fchown fchmod fsync getcwd getpseudotty \
        getpwent getpwnam getpwuid getrlimit gettimeofday localtime_r lstat \
        memset mkdtemp nanosleep opendir putenv qsort readlink select setenv \
        getpgid setpgid setsid sigaltstack sigstack sigset sigsetjmp sigaction \
-       sigprocmask sigvec strcasecmp strerror strftime stricmp strncasecmp \
+       sigprocmask sigvec strcasecmp strcoll strerror strftime stricmp strncasecmp \
        strnicmp strpbrk strptime strtol tgetent towlower towupper iswupper \
        tzset usleep utime utimes mblen ftruncate unsetenv posix_openpt)
 AC_FUNC_SELECT_ARGTYPES
index fe98636598d4404279acdc7cd0d17a5f5f345c92..230962e94da626e2020134d1adad7c793b567aa4 100644 (file)
@@ -769,8 +769,8 @@ 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},
+    {"readdir",                1, 3, FEARG_1,    ret_list_string, f_readdir},
+    {"readdirex",      1, 3, FEARG_1,    ret_list_dict_any, f_readdirex},
     {"readfile",       1, 3, FEARG_1,    ret_any,      f_readfile},
     {"reduce",         2, 3, FEARG_1,    ret_any,      f_reduce},
     {"reg_executing",  0, 0, 0,          ret_string,   f_reg_executing},
index d2c71d290bc2eef959d107e5f66775e0cd45f69a..0a0bbc44abc77be08ddaa26be045fdba87be58d3 100644 (file)
@@ -145,6 +145,7 @@ static struct vimvar
     {VV_NAME("versionlong",     VAR_NUMBER), VV_RO},
     {VV_NAME("echospace",       VAR_NUMBER), VV_RO},
     {VV_NAME("argv",            VAR_LIST), VV_RO},
+    {VV_NAME("collate",                 VAR_STRING), VV_RO},
 };
 
 // shorthand
index d83facf0d1cb1f4b90feecf5479329a17d3c8ca4..f0b5bfd45199ff6a7f432d000889f3dbd11e557e 100644 (file)
@@ -1185,6 +1185,14 @@ set_lang_var(void)
     loc = get_locale_val(LC_TIME);
 # endif
     set_vim_var_string(VV_LC_TIME, loc, -1);
+
+# ifdef HAVE_GET_LOCALE_VAL
+    loc = get_locale_val(LC_COLLATE);
+# else
+    // setlocale() not supported: use the default value
+    loc = (char_u *)"C";
+# endif
+    set_vim_var_string(VV_COLLATE, loc, -1);
 }
 #endif
 
@@ -1232,6 +1240,12 @@ ex_language(exarg_T *eap)
            name = skipwhite(p);
            whatstr = "time ";
        }
+       else if (STRNICMP(eap->arg, "collate", p - eap->arg) == 0)
+       {
+           what = LC_COLLATE;
+           name = skipwhite(p);
+           whatstr = "collate ";
+       }
     }
 
     if (*name == NUL)
@@ -1274,7 +1288,7 @@ ex_language(exarg_T *eap)
            // Reset $LC_ALL, otherwise it would overrule everything.
            vim_setenv((char_u *)"LC_ALL", (char_u *)"");
 
-           if (what != LC_TIME)
+           if (what != LC_TIME && what != LC_COLLATE)
            {
                // Tell gettext() what to translate to.  It apparently doesn't
                // use the currently effective locale.  Also do this when
@@ -1309,7 +1323,7 @@ ex_language(exarg_T *eap)
            }
 
 # ifdef FEAT_EVAL
-           // Set v:lang, v:lc_time and v:ctype to the final result.
+           // Set v:lang, v:lc_time, v:collate and v:ctype to the final result.
            set_lang_var();
 # endif
 # ifdef FEAT_TITLE
@@ -1462,11 +1476,13 @@ get_lang_arg(expand_T *xp UNUSED, int idx)
        return (char_u *)"ctype";
     if (idx == 2)
        return (char_u *)"time";
+    if (idx == 3)
+       return (char_u *)"collate";
 
     init_locales();
     if (locales == NULL)
        return NULL;
-    return locales[idx - 3];
+    return locales[idx - 4];
 }
 
 /*
index 7b605296b74aba111e45f25fa5943ba12ece0fb4..395e54b6a44dc3b097b545481115ff0410fd9418 100644 (file)
@@ -35,6 +35,10 @@ static linenr_T readfile_linenr(linenr_T linecnt, char_u *p, char_u *endp);
 static char_u *check_for_bom(char_u *p, long size, int *lenp, int flags);
 static char *e_auchangedbuf = N_("E812: Autocommands changed buffer or buffer name");
 
+#ifdef FEAT_EVAL
+static int readdirex_sort;
+#endif
+
     void
 filemess(
     buf_T      *buf,
@@ -4645,7 +4649,23 @@ compare_readdirex_item(const void *p1, const void *p2)
 
     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);
+    if (readdirex_sort == READDIR_SORT_BYTE)
+       return STRCMP(name1, name2);
+    else if (readdirex_sort == READDIR_SORT_IC)
+       return STRICMP(name1, name2);
+    else
+       return STRCOLL(name1, name2);
+}
+
+    static int
+compare_readdir_item(const void *s1, const void *s2)
+{
+    if (readdirex_sort == READDIR_SORT_BYTE)
+       return STRCMP(*(char **)s1, *(char **)s2);
+    else if (readdirex_sort == READDIR_SORT_IC)
+       return STRICMP(*(char **)s1, *(char **)s2);
+    else
+       return STRCOLL(*(char **)s1, *(char **)s2);
 }
 #endif
 
@@ -4663,7 +4683,8 @@ readdir_core(
     char_u     *path,
     int                withattr UNUSED,
     void       *context,
-    int                (*checkitem)(void *context, void *item))
+    int                (*checkitem)(void *context, void *item),
+    int         sort)
 {
     int                        failed = FALSE;
     char_u             *p;
@@ -4687,6 +4708,8 @@ readdir_core(
        else \
            vim_free(item); \
     } while (0)
+
+    readdirex_sort = READDIR_SORT_BYTE;
 # else
 #  define FREE_ITEM(item)   vim_free(item)
 # endif
@@ -4844,15 +4867,19 @@ readdir_core(
 
 # undef FREE_ITEM
 
-    if (!failed && gap->ga_len > 0)
+    if (!failed && gap->ga_len > 0 && sort > READDIR_SORT_NONE)
     {
 # ifdef FEAT_EVAL
+       readdirex_sort = sort;
        if (withattr)
            qsort((void*)gap->ga_data, (size_t)gap->ga_len, sizeof(dict_T*),
                    compare_readdirex_item);
        else
-# endif
+           qsort((void*)gap->ga_data, (size_t)gap->ga_len, sizeof(char_u *),
+                   compare_readdir_item);
+# else
            sort_strings((char_u **)gap->ga_data, gap->ga_len);
+# endif
     }
 
     return failed ? FAIL : OK;
@@ -4883,7 +4910,7 @@ delete_recursive(char_u *name)
        exp = vim_strsave(name);
        if (exp == NULL)
            return -1;
-       if (readdir_core(&ga, exp, FALSE, NULL, NULL) == OK)
+       if (readdir_core(&ga, exp, FALSE, NULL, NULL, READDIR_SORT_NONE) == OK)
        {
            for (i = 0; i < ga.ga_len; ++i)
            {
index 90d04cfcf1f73e40cc488e64b5d615c1741a0dbd..1fe757e850273412f8f82cff86ce8ae049e80137 100644 (file)
@@ -1405,6 +1405,36 @@ theend:
     return retval;
 }
 
+    static int
+readdirex_dict_arg(typval_T *tv, int *cmp)
+{
+    char_u     *compare;
+
+    if (tv->v_type != VAR_DICT)
+    {
+       emsg(_(e_dictreq));
+       return FAIL;
+    }
+
+    if (dict_find(tv->vval.v_dict, (char_u *)"sort", -1) != NULL)
+       compare = dict_get_string(tv->vval.v_dict, (char_u *)"sort", FALSE);
+    else
+    {
+       semsg(_(e_no_dict_key), "sort");
+       return FAIL;
+    }
+
+    if (STRCMP(compare, (char_u *) "none") == 0)
+       *cmp = READDIR_SORT_NONE;
+    else if (STRCMP(compare, (char_u *) "case") == 0)
+       *cmp = READDIR_SORT_BYTE;
+    else if (STRCMP(compare, (char_u *) "icase") == 0)
+       *cmp = READDIR_SORT_IC;
+    else if (STRCMP(compare, (char_u *) "collate") == 0)
+       *cmp = READDIR_SORT_COLLATE;
+    return OK;
+}
+
 /*
  * "readdir()" function
  */
@@ -1417,14 +1447,19 @@ f_readdir(typval_T *argvars, typval_T *rettv)
     char_u     *p;
     garray_T   ga;
     int                i;
+    int         sort = READDIR_SORT_BYTE;
 
     if (rettv_list_alloc(rettv) == FAIL)
        return;
     path = tv_get_string(&argvars[0]);
     expr = &argvars[1];
 
+    if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN &&
+           readdirex_dict_arg(&argvars[2], &sort) == FAIL)
+       return;
+
     ret = readdir_core(&ga, path, FALSE, (void *)expr,
-           (expr->v_type == VAR_UNKNOWN) ? NULL : readdir_checkitem);
+           (expr->v_type == VAR_UNKNOWN) ? NULL : readdir_checkitem, sort);
     if (ret == OK)
     {
        for (i = 0; i < ga.ga_len; i++)
@@ -1480,14 +1515,19 @@ f_readdirex(typval_T *argvars, typval_T *rettv)
     char_u     *path;
     garray_T   ga;
     int                i;
+    int         sort = READDIR_SORT_BYTE;
 
     if (rettv_list_alloc(rettv) == FAIL)
        return;
     path = tv_get_string(&argvars[0]);
     expr = &argvars[1];
 
+    if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN &&
+           readdirex_dict_arg(&argvars[2], &sort) == FAIL)
+       return;
+
     ret = readdir_core(&ga, path, TRUE, (void *)expr,
-           (expr->v_type == VAR_UNKNOWN) ? NULL : readdirex_checkitem);
+           (expr->v_type == VAR_UNKNOWN) ? NULL : readdirex_checkitem, sort);
     if (ret == OK)
     {
        for (i = 0; i < ga.ga_len; i++)
index cb85bccc430ebae19120771d424ead387def1ed1..bbc12cea38644ea84472dbf0687dc78c8577211b 100644 (file)
@@ -1699,6 +1699,8 @@ EXTERN char e_const_option[]      INIT(= N_("E996: Cannot lock an option"));
 EXTERN char e_unknown_option[] INIT(= N_("E113: Unknown option: %s"));
 EXTERN char e_letunexp[]       INIT(= N_("E18: Unexpected characters in :let"));
 EXTERN char e_reduceempty[]    INIT(= N_("E998: Reduce of an empty %s with no initial value"));
+// TODO: Change Error Number
+EXTERN char e_no_dict_key[]    INIT(= N_("E999: Dictionary with key \"%s\" required"));
 #endif
 #ifdef FEAT_QUICKFIX
 EXTERN char e_readerrf[]       INIT(= N_("E47: Error while reading errorfile"));
index 2171e6b80481276050923c8bddaa394bebac04d1..cdf92743b56ec9489935c76a90bfc12ae3ca1d05 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, int withattr, void *context, int (*checkitem)(void *context, void *item));
+int readdir_core(garray_T *gap, char_u *path, int withattr, void *context, int (*checkitem)(void *context, void *item), int sort);
 int delete_recursive(char_u *name);
 void vim_deltempdir(void);
 char_u *vim_tempname(int extra_char, int keep);
index 98af482e99bff5e5c82b4b3f9cdef10922b3008c..eeb9c2f5485436c854132e384cf32b3d445b8436 100644 (file)
@@ -604,10 +604,20 @@ func Test_cmdline_complete_bang()
 endfunc
 
 func Test_cmdline_complete_languages()
+  let lang = substitute(execute('language time'), '.*"\(.*\)"$', '\1', '')
+  call assert_equal(lang, v:lc_time)
+
+  let lang = substitute(execute('language ctype'), '.*"\(.*\)"$', '\1', '')
+  call assert_equal(lang, v:ctype)
+
+  let lang = substitute(execute('language collate'), '.*"\(.*\)"$', '\1', '')
+  call assert_equal(lang, v:collate)
+
   let lang = substitute(execute('language messages'), '.*"\(.*\)"$', '\1', '')
+  call assert_equal(lang, v:lang)
 
   call feedkeys(":language \<c-a>\<c-b>\"\<cr>", 'tx')
-  call assert_match('^"language .*\<ctype\>.*\<messages\>.*\<time\>', @:)
+  call assert_match('^"language .*\<collate\>.*\<ctype\>.*\<messages\>.*\<time\>', @:)
 
   call assert_match('^"language .*\<' . lang . '\>', @:)
 
@@ -619,6 +629,9 @@ func Test_cmdline_complete_languages()
 
   call feedkeys(":language time \<c-a>\<c-b>\"\<cr>", 'tx')
   call assert_match('^"language .*\<' . lang . '\>', @:)
+
+  call feedkeys(":language collate \<c-a>\<c-b>\"\<cr>", 'tx')
+  call assert_match('^"language .*\<' . lang . '\>', @:)
 endfunc
 
 func Test_cmdline_complete_env_variable()
index d9f8b9ac0375c4f87295d5a62f3cb9a9400f2b76..cefeaf7f90d1a65719c9d633d2900b6a987b487e 100644 (file)
@@ -1937,6 +1937,85 @@ func Test_readdirex()
   eval 'Xdir'->delete('rf')
 endfunc
 
+func Test_readdirex_sort()
+  CheckUnix
+  " Skip tests on Mac OS X and Cygwin (does not allow several files with different casing)
+  if has("osxdarwin") || has("osx") || has("macunix") || has("win32unix")
+    throw 'Skipped: Test_readdirex_sort on systems that do not allow this using the default filesystem'
+  endif
+  let _collate = v:collate
+  call mkdir('Xdir2')
+  call writefile(['1'], 'Xdir2/README.txt')
+  call writefile(['2'], 'Xdir2/Readme.txt')
+  call writefile(['3'], 'Xdir2/readme.txt')
+
+  " 1) default
+  let files = readdirex('Xdir2')->map({-> v:val.name})
+  let default = copy(files)
+  call assert_equal(['README.txt', 'Readme.txt', 'readme.txt'], files, 'sort using default')
+
+  " 2) no sorting
+  let files = readdirex('Xdir2', 1, #{sort: 'none'})->map({-> v:val.name})
+  let unsorted = copy(files)
+  call assert_equal(['README.txt', 'Readme.txt', 'readme.txt'], sort(files), 'unsorted')
+
+  " 3) sort by case (same as default)
+  let files = readdirex('Xdir2', 1, #{sort: 'case'})->map({-> v:val.name})
+  call assert_equal(default, files, 'sort by case')
+
+  " 4) sort by ignoring case
+  let files = readdirex('Xdir2', 1, #{sort: 'icase'})->map({-> v:val.name})
+  call assert_equal(unsorted->sort('i'), files, 'sort by icase')
+
+  " 5) Default Collation
+  let collate = v:collate
+  lang collate C
+  let files = readdirex('Xdir2', 1, #{sort: 'collate'})->map({-> v:val.name})
+  call assert_equal(['README.txt', 'Readme.txt', 'readme.txt'], files, 'sort by C collation')
+
+  " 6) Collation de_DE
+  " Switch locale, this may not work on the CI system, if the locale isn't
+  " available
+  try
+    lang collate de_DE
+    let files = readdirex('Xdir2', 1, #{sort: 'collate'})->map({-> v:val.name})
+    call assert_equal(['readme.txt', 'Readme.txt', 'README.txt'], files, 'sort by de_DE collation')
+  catch
+    throw 'Skipped: de_DE collation is not available'
+
+  finally
+    exe 'lang collate' collate
+    eval 'Xdir2'->delete('rf')
+  endtry
+endfunc
+
+func Test_readdir_sort()
+  " some more cases for testing sorting for readdirex
+  let dir = 'Xdir3'
+  call mkdir(dir)
+  call writefile(['1'], dir .. '/README.txt')
+  call writefile(['2'], dir .. '/Readm.txt')
+  call writefile(['3'], dir .. '/read.txt')
+  call writefile(['4'], dir .. '/Z.txt')
+  call writefile(['5'], dir .. '/a.txt')
+  call writefile(['6'], dir .. '/b.txt')
+
+  " 1) default
+  let files = readdir(dir)
+  let default = copy(files)
+  call assert_equal(default->sort(), files, 'sort using default')
+
+  " 2) sort by case (same as default)
+  let files = readdir(dir, '1', #{sort: 'case'})
+  call assert_equal(default, files, 'sort using default')
+
+  " 3) sort by ignoring case
+  let files = readdir(dir, '1', #{sort: 'icase'})
+  call assert_equal(default->sort('i'), files, 'sort by ignoring case')
+
+  eval dir->delete('rf')
+endfunc
+
 func Test_delete_rf()
   call mkdir('Xdir')
   call writefile([], 'Xdir/foo.txt')
index 979ce701fe8de95f42cf290dacbaba5ee0de868a..e1a41fb435a2c54ebd53c4672de0ae5e6d4d55ac 100644 (file)
@@ -754,6 +754,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    988,
 /**/
     987,
 /**/
index 0204aa306ebf2f28741ff49557e56daf9bcf1dd2..2c2848cc7cf76e574e5bc51523563e53e56fa119 100644 (file)
--- a/src/vim.h
+++ b/src/vim.h
@@ -1599,6 +1599,11 @@ void *vim_memset(void *, int, size_t);
 #  define STRICMP(d, s)            vim_stricmp((char *)(d), (char *)(s))
 # endif
 #endif
+#ifdef HAVE_STRCOLL
+# define STRCOLL(d, s)     strcoll((char *)(d), (char *)(s))
+#else
+# define STRCOLL(d, s)     strcmp((char *)(d), (char *)(s))
+#endif
 
 // Like strcpy() but allows overlapped source and destination.
 #define STRMOVE(d, s)      mch_memmove((d), (s), STRLEN(s) + 1)
@@ -1896,7 +1901,7 @@ typedef int sock_T;
 #define VALID_PATH             1
 #define VALID_HEAD             2
 
-// Defines for Vim variables.  These must match vimvars[] in eval.c!
+// Defines for Vim variables.  These must match vimvars[] in evalvars.c!
 #define VV_COUNT       0
 #define VV_COUNT1      1
 #define VV_PREVCOUNT   2
@@ -1992,7 +1997,8 @@ typedef int sock_T;
 #define VV_VERSIONLONG 92
 #define VV_ECHOSPACE   93
 #define VV_ARGV                94
-#define VV_LEN         95      // number of v: vars
+#define VV_COLLATE      95
+#define VV_LEN         96      // number of v: vars
 
 // used for v_number in VAR_BOOL and VAR_SPECIAL
 #define VVAL_FALSE     0L      // VAR_BOOL
@@ -2669,4 +2675,10 @@ long elapsed(DWORD start_tick);
 #define FSK_IN_STRING  0x04    // TRUE in string, double quote is escaped
 #define FSK_SIMPLIFY   0x08    // simplify <C-H> and <A-x>
 
+// Flags for the readdirex function, how to sort the result
+#define READDIR_SORT_NONE      0  // do not sort
+#define READDIR_SORT_BYTE      1  // sort by byte order (strcmp), default
+#define READDIR_SORT_IC                2  // sort ignoring case (strcasecmp)
+#define READDIR_SORT_COLLATE   3  // sort according to collation (strcoll)
+
 #endif // VIM__H