diff options
author | Bram Moolenaar <Bram@vim.org> | 2020-06-16 20:03:43 +0200 |
---|---|---|
committer | Bram Moolenaar <Bram@vim.org> | 2020-06-16 20:03:43 +0200 |
commit | 84cf6bd81bec93b49166cd48fccc7087fdbaa6fc (patch) | |
tree | 63d744fbf9ac7db5246a60b48aa0396aaaa22c18 | |
parent | 9af78769eeae0318e07aa8b6af4d6e2244481ca7 (diff) | |
download | vim-git-84cf6bd81bec93b49166cd48fccc7087fdbaa6fc.tar.gz |
patch 8.2.0988: getting directory contents is always case sortedv8.2.0988
Problem: Getting directory contents is always case sorted.
Solution: Add sort options and v:collate. (Christian Brabandt, closes #6229)
-rw-r--r-- | runtime/doc/eval.txt | 60 | ||||
-rw-r--r-- | runtime/doc/mlang.txt | 12 | ||||
-rwxr-xr-x | src/auto/configure | 2 | ||||
-rw-r--r-- | src/cmdexpand.c | 3 | ||||
-rw-r--r-- | src/config.h.in | 1 | ||||
-rw-r--r-- | src/configure.ac | 2 | ||||
-rw-r--r-- | src/evalfunc.c | 4 | ||||
-rw-r--r-- | src/evalvars.c | 1 | ||||
-rw-r--r-- | src/ex_cmds2.c | 22 | ||||
-rw-r--r-- | src/fileio.c | 37 | ||||
-rw-r--r-- | src/filepath.c | 44 | ||||
-rw-r--r-- | src/globals.h | 2 | ||||
-rw-r--r-- | src/proto/fileio.pro | 2 | ||||
-rw-r--r-- | src/testdir/test_cmdline.vim | 15 | ||||
-rw-r--r-- | src/testdir/test_functions.vim | 79 | ||||
-rw-r--r-- | src/version.c | 2 | ||||
-rw-r--r-- | src/vim.h | 16 |
17 files changed, 272 insertions, 32 deletions
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 816aeca14..4ab8bbd1e 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -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: @@ -7987,6 +8020,11 @@ readdirex({directory} [, {expr}]) *readdirex()* 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() < diff --git a/runtime/doc/mlang.txt b/runtime/doc/mlang.txt index 629907c16..b453a39e3 100644 --- a/runtime/doc/mlang.txt +++ b/runtime/doc/mlang.txt @@ -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 diff --git a/src/auto/configure b/src/auto/configure index 4c3ff325d..f642a0b5f 100755 --- a/src/auto/configure +++ b/src/auto/configure @@ -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 : diff --git a/src/cmdexpand.c b/src/cmdexpand.c index b0ea057ae..9508d669d 100644 --- a/src/cmdexpand.c +++ b/src/cmdexpand.c @@ -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); diff --git a/src/config.h.in b/src/config.h.in index 7055b6f00..b11448021 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -198,6 +198,7 @@ #undef HAVE_SIGVEC #undef HAVE_SMACK #undef HAVE_STRCASECMP +#undef HAVE_STRCOLL #undef HAVE_STRERROR #undef HAVE_STRFTIME #undef HAVE_STRICMP diff --git a/src/configure.ac b/src/configure.ac index dccf3dce8..054391882 100644 --- a/src/configure.ac +++ b/src/configure.ac @@ -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 diff --git a/src/evalfunc.c b/src/evalfunc.c index fe9863659..230962e94 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -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}, diff --git a/src/evalvars.c b/src/evalvars.c index d2c71d290..0a0bbc44a 100644 --- a/src/evalvars.c +++ b/src/evalvars.c @@ -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 diff --git a/src/ex_cmds2.c b/src/ex_cmds2.c index d83facf0d..f0b5bfd45 100644 --- a/src/ex_cmds2.c +++ b/src/ex_cmds2.c @@ -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]; } /* diff --git a/src/fileio.c b/src/fileio.c index 7b605296b..395e54b6a 100644 --- a/src/fileio.c +++ b/src/fileio.c @@ -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) { diff --git a/src/filepath.c b/src/filepath.c index 90d04cfcf..1fe757e85 100644 --- a/src/filepath.c +++ b/src/filepath.c @@ -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++) diff --git a/src/globals.h b/src/globals.h index cb85bccc4..bbc12cea3 100644 --- a/src/globals.h +++ b/src/globals.h @@ -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")); diff --git a/src/proto/fileio.pro b/src/proto/fileio.pro index 2171e6b80..cdf92743b 100644 --- a/src/proto/fileio.pro +++ b/src/proto/fileio.pro @@ -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); diff --git a/src/testdir/test_cmdline.vim b/src/testdir/test_cmdline.vim index 98af482e9..eeb9c2f54 100644 --- a/src/testdir/test_cmdline.vim +++ b/src/testdir/test_cmdline.vim @@ -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() diff --git a/src/testdir/test_functions.vim b/src/testdir/test_functions.vim index d9f8b9ac0..cefeaf7f9 100644 --- a/src/testdir/test_functions.vim +++ b/src/testdir/test_functions.vim @@ -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') diff --git a/src/version.c b/src/version.c index 979ce701f..e1a41fb43 100644 --- a/src/version.c +++ b/src/version.c @@ -755,6 +755,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 988, +/**/ 987, /**/ 986, @@ -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 |