Skip to content

Commit 84cf6bd

Browse files
committed
patch 8.2.0988: getting directory contents is always case sorted
Problem: Getting directory contents is always case sorted. Solution: Add sort options and v:collate. (Christian Brabandt, closes #6229)
1 parent 9af7876 commit 84cf6bd

File tree

17 files changed

+272
-32
lines changed

17 files changed

+272
-32
lines changed

runtime/doc/eval.txt

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1745,6 +1745,14 @@ v:cmdbang Set like v:cmdarg for a file read/write command. When a "!"
17451745
was used the value is 1, otherwise it is 0. Note that this
17461746
can only be used in autocommands. For user commands |<bang>|
17471747
can be used.
1748+
*v:collate* *collate-variable*
1749+
v:collate The current locale setting for collation order of the runtime
1750+
environment. This allows Vim scripts to be aware of the
1751+
current locale encoding. Technical: it's the value of
1752+
LC_COLLATE. When not using a locale the value is "C".
1753+
This variable can not be set directly, use the |:language|
1754+
command.
1755+
See |multi-lang|.
17481756

17491757
*v:completed_item* *completed_item-variable*
17501758
v:completed_item
@@ -2683,8 +2691,10 @@ pyxeval({expr}) any evaluate |python_x| expression
26832691
rand([{expr}]) Number get pseudo-random number
26842692
range({expr} [, {max} [, {stride}]])
26852693
List items from {expr} to {max}
2686-
readdir({dir} [, {expr}]) List file names in {dir} selected by {expr}
2687-
readdirex({dir} [, {expr}]) List file info in {dir} selected by {expr}
2694+
readdir({dir} [, {expr} [, {dict}]])
2695+
List file names in {dir} selected by {expr}
2696+
readdirex({dir} [, {expr} [, {dict}]])
2697+
List file info in {dir} selected by {expr}
26882698
readfile({fname} [, {type} [, {max}]])
26892699
List get list of lines from file {fname}
26902700
reduce({object}, {func} [, {initial}])
@@ -7904,11 +7914,12 @@ rand([{expr}]) *rand()* *random*
79047914
:echo rand(seed)
79057915
:echo rand(seed) % 16 " random number 0 - 15
79067916
<
7907-
readdir({directory} [, {expr}]) *readdir()*
7917+
readdir({directory} [, {expr} [, {dict}]]) *readdir()*
79087918
Return a list with file and directory names in {directory}.
79097919
You can also use |glob()| if you don't need to do complicated
79107920
things, such as limiting the number of matches.
7911-
The list will be sorted (case sensitive).
7921+
The list will be sorted (case sensitive), see the {dict}
7922+
argument below for changing the sort order.
79127923

79137924
When {expr} is omitted all entries are included.
79147925
When {expr} is given, it is evaluated to check what to do:
@@ -7926,18 +7937,38 @@ readdir({directory} [, {expr}]) *readdir()*
79267937
< To skip hidden and backup files: >
79277938
readdir(dirname, {n -> n !~ '^\.\|\~$'})
79287939

7940+
< The optional {dict} argument allows for further custom
7941+
values. Currently this is used to specify if and how sorting
7942+
should be performed. The dict can have the following members:
7943+
7944+
sort How to sort the result returned from the system.
7945+
Valid values are:
7946+
"none" do not sort (fastest method)
7947+
"case" sort case sensitive (byte value of
7948+
each character, technically, using
7949+
strcmp()) (default)
7950+
"icase" sort case insensitive (technically
7951+
using strcasecmp())
7952+
"collate" sort using the collation order
7953+
of the "POSIX" or "C" |locale|
7954+
(technically using strcoll())
7955+
Other values are silently ignored.
7956+
7957+
For example, to get a list of all files in the current
7958+
directory without sorting the individual entries: >
7959+
readdir('.', '1', #{sort: 'none'})
79297960
< If you want to get a directory tree: >
7930-
function! s:tree(dir)
7931-
return {a:dir : map(readdir(a:dir),
7961+
function! s:tree(dir)
7962+
return {a:dir : map(readdir(a:dir),
79327963
\ {_, x -> isdirectory(x) ?
7933-
\ {x : s:tree(a:dir . '/' . x)} : x})}
7934-
endfunction
7935-
echo s:tree(".")
7964+
\ {x : s:tree(a:dir . '/' . x)} : x})}
7965+
endfunction
7966+
echo s:tree(".")
79367967
<
79377968
Can also be used as a |method|: >
79387969
GetDirName()->readdir()
79397970
<
7940-
readdirex({directory} [, {expr}]) *readdirex()*
7971+
readdirex({directory} [, {expr} [, {dict}]]) *readdirex()*
79417972
Extended version of |readdir()|.
79427973
Return a list of Dictionaries with file and directory
79437974
information in {directory}.
@@ -7946,7 +7977,9 @@ readdirex({directory} [, {expr}]) *readdirex()*
79467977
This is much faster than calling |readdir()| then calling
79477978
|getfperm()|, |getfsize()|, |getftime()| and |getftype()| for
79487979
each file and directory especially on MS-Windows.
7949-
The list will be sorted by name (case sensitive).
7980+
The list will by default be sorted by name (case sensitive),
7981+
the sorting can be changed by using the optional {dict}
7982+
argument, see |readdir()|.
79507983

79517984
The Dictionary for file and directory information has the
79527985
following items:
@@ -7986,6 +8019,11 @@ readdirex({directory} [, {expr}]) *readdirex()*
79868019
When {expr} is a function the entry is passed as the argument.
79878020
For example, to get a list of files ending in ".txt": >
79888021
readdirex(dirname, {e -> e.name =~ '.txt$'})
8022+
<
8023+
For example, to get a list of all files in the current
8024+
directory without sorting the individual entries: >
8025+
readdirex(dirname, '1', #{sort: 'none'})
8026+
79898027
<
79908028
Can also be used as a |method|: >
79918029
GetDirName()->readdirex()

runtime/doc/mlang.txt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,22 +37,27 @@ use of "-" and "_".
3737
:lan[guage] mes[sages]
3838
:lan[guage] cty[pe]
3939
:lan[guage] tim[e]
40+
:lan[guage] col[late]
4041
Print the current language (aka locale).
4142
With the "messages" argument the language used for
4243
messages is printed. Technical: LC_MESSAGES.
4344
With the "ctype" argument the language used for
4445
character encoding is printed. Technical: LC_CTYPE.
4546
With the "time" argument the language used for
4647
strftime() is printed. Technical: LC_TIME.
48+
With the "collate" argument the language used for
49+
collation order is printed. Technical: LC_COLLATE.
4750
Without argument all parts of the locale are printed
4851
(this is system dependent).
4952
The current language can also be obtained with the
50-
|v:lang|, |v:ctype| and |v:lc_time| variables.
53+
|v:lang|, |v:ctype|, |v:collate| and |v:lc_time|
54+
variables.
5155

5256
:lan[guage] {name}
5357
:lan[guage] mes[sages] {name}
5458
:lan[guage] cty[pe] {name}
5559
:lan[guage] tim[e] {name}
60+
:lan[guage] col[late] {name}
5661
Set the current language (aka locale) to {name}.
5762
The locale {name} must be a valid locale on your
5863
system. Some systems accept aliases like "en" or
@@ -72,7 +77,10 @@ use of "-" and "_".
7277
With the "time" argument the language used for time
7378
and date messages is set. This affects strftime().
7479
This sets $LC_TIME.
75-
Without an argument both are set, and additionally
80+
With the "collate" argument the language used for the
81+
collation order is set. This affects sorting of
82+
characters. This sets $LC_COLLATE.
83+
Without an argument all are set, and additionally
7684
$LANG is set.
7785
When compiled with the |+float| feature the LC_NUMERIC
7886
value will always be set to "C", so that floating

src/auto/configure

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12618,7 +12618,7 @@ for ac_func in fchdir fchown fchmod fsync getcwd getpseudotty \
1261812618
getpwent getpwnam getpwuid getrlimit gettimeofday localtime_r lstat \
1261912619
memset mkdtemp nanosleep opendir putenv qsort readlink select setenv \
1262012620
getpgid setpgid setsid sigaltstack sigstack sigset sigsetjmp sigaction \
12621-
sigprocmask sigvec strcasecmp strerror strftime stricmp strncasecmp \
12621+
sigprocmask sigvec strcasecmp strcoll strerror strftime stricmp strncasecmp \
1262212622
strnicmp strpbrk strptime strtol tgetent towlower towupper iswupper \
1262312623
tzset usleep utime utimes mblen ftruncate unsetenv posix_openpt
1262412624
do :

src/cmdexpand.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1728,7 +1728,8 @@ set_one_cmd_context(
17281728
{
17291729
if ( STRNCMP(arg, "messages", p - arg) == 0
17301730
|| STRNCMP(arg, "ctype", p - arg) == 0
1731-
|| STRNCMP(arg, "time", p - arg) == 0)
1731+
|| STRNCMP(arg, "time", p - arg) == 0
1732+
|| STRNCMP(arg, "collate", p - arg) == 0)
17321733
{
17331734
xp->xp_context = EXPAND_LOCALES;
17341735
xp->xp_pattern = skipwhite(p);

src/config.h.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@
198198
#undef HAVE_SIGVEC
199199
#undef HAVE_SMACK
200200
#undef HAVE_STRCASECMP
201+
#undef HAVE_STRCOLL
201202
#undef HAVE_STRERROR
202203
#undef HAVE_STRFTIME
203204
#undef HAVE_STRICMP

src/configure.ac

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3739,7 +3739,7 @@ AC_CHECK_FUNCS(fchdir fchown fchmod fsync getcwd getpseudotty \
37393739
getpwent getpwnam getpwuid getrlimit gettimeofday localtime_r lstat \
37403740
memset mkdtemp nanosleep opendir putenv qsort readlink select setenv \
37413741
getpgid setpgid setsid sigaltstack sigstack sigset sigsetjmp sigaction \
3742-
sigprocmask sigvec strcasecmp strerror strftime stricmp strncasecmp \
3742+
sigprocmask sigvec strcasecmp strcoll strerror strftime stricmp strncasecmp \
37433743
strnicmp strpbrk strptime strtol tgetent towlower towupper iswupper \
37443744
tzset usleep utime utimes mblen ftruncate unsetenv posix_openpt)
37453745
AC_FUNC_SELECT_ARGTYPES

src/evalfunc.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -769,8 +769,8 @@ static funcentry_T global_functions[] =
769769
},
770770
{"rand", 0, 1, FEARG_1, ret_number, f_rand},
771771
{"range", 1, 3, FEARG_1, ret_list_number, f_range},
772-
{"readdir", 1, 2, FEARG_1, ret_list_string, f_readdir},
773-
{"readdirex", 1, 2, FEARG_1, ret_list_dict_any, f_readdirex},
772+
{"readdir", 1, 3, FEARG_1, ret_list_string, f_readdir},
773+
{"readdirex", 1, 3, FEARG_1, ret_list_dict_any, f_readdirex},
774774
{"readfile", 1, 3, FEARG_1, ret_any, f_readfile},
775775
{"reduce", 2, 3, FEARG_1, ret_any, f_reduce},
776776
{"reg_executing", 0, 0, 0, ret_string, f_reg_executing},

src/evalvars.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ static struct vimvar
145145
{VV_NAME("versionlong", VAR_NUMBER), VV_RO},
146146
{VV_NAME("echospace", VAR_NUMBER), VV_RO},
147147
{VV_NAME("argv", VAR_LIST), VV_RO},
148+
{VV_NAME("collate", VAR_STRING), VV_RO},
148149
};
149150

150151
// shorthand

src/ex_cmds2.c

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1185,6 +1185,14 @@ set_lang_var(void)
11851185
loc = get_locale_val(LC_TIME);
11861186
# endif
11871187
set_vim_var_string(VV_LC_TIME, loc, -1);
1188+
1189+
# ifdef HAVE_GET_LOCALE_VAL
1190+
loc = get_locale_val(LC_COLLATE);
1191+
# else
1192+
// setlocale() not supported: use the default value
1193+
loc = (char_u *)"C";
1194+
# endif
1195+
set_vim_var_string(VV_COLLATE, loc, -1);
11881196
}
11891197
#endif
11901198

@@ -1232,6 +1240,12 @@ ex_language(exarg_T *eap)
12321240
name = skipwhite(p);
12331241
whatstr = "time ";
12341242
}
1243+
else if (STRNICMP(eap->arg, "collate", p - eap->arg) == 0)
1244+
{
1245+
what = LC_COLLATE;
1246+
name = skipwhite(p);
1247+
whatstr = "collate ";
1248+
}
12351249
}
12361250

12371251
if (*name == NUL)
@@ -1274,7 +1288,7 @@ ex_language(exarg_T *eap)
12741288
// Reset $LC_ALL, otherwise it would overrule everything.
12751289
vim_setenv((char_u *)"LC_ALL", (char_u *)"");
12761290

1277-
if (what != LC_TIME)
1291+
if (what != LC_TIME && what != LC_COLLATE)
12781292
{
12791293
// Tell gettext() what to translate to. It apparently doesn't
12801294
// use the currently effective locale. Also do this when
@@ -1309,7 +1323,7 @@ ex_language(exarg_T *eap)
13091323
}
13101324

13111325
# ifdef FEAT_EVAL
1312-
// Set v:lang, v:lc_time and v:ctype to the final result.
1326+
// Set v:lang, v:lc_time, v:collate and v:ctype to the final result.
13131327
set_lang_var();
13141328
# endif
13151329
# ifdef FEAT_TITLE
@@ -1462,11 +1476,13 @@ get_lang_arg(expand_T *xp UNUSED, int idx)
14621476
return (char_u *)"ctype";
14631477
if (idx == 2)
14641478
return (char_u *)"time";
1479+
if (idx == 3)
1480+
return (char_u *)"collate";
14651481

14661482
init_locales();
14671483
if (locales == NULL)
14681484
return NULL;
1469-
return locales[idx - 3];
1485+
return locales[idx - 4];
14701486
}
14711487

14721488
/*

src/fileio.c

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ static linenr_T readfile_linenr(linenr_T linecnt, char_u *p, char_u *endp);
3535
static char_u *check_for_bom(char_u *p, long size, int *lenp, int flags);
3636
static char *e_auchangedbuf = N_("E812: Autocommands changed buffer or buffer name");
3737

38+
#ifdef FEAT_EVAL
39+
static int readdirex_sort;
40+
#endif
41+
3842
void
3943
filemess(
4044
buf_T *buf,
@@ -4645,7 +4649,23 @@ compare_readdirex_item(const void *p1, const void *p2)
46454649

46464650
name1 = dict_get_string(*(dict_T**)p1, (char_u*)"name", FALSE);
46474651
name2 = dict_get_string(*(dict_T**)p2, (char_u*)"name", FALSE);
4648-
return STRCMP(name1, name2);
4652+
if (readdirex_sort == READDIR_SORT_BYTE)
4653+
return STRCMP(name1, name2);
4654+
else if (readdirex_sort == READDIR_SORT_IC)
4655+
return STRICMP(name1, name2);
4656+
else
4657+
return STRCOLL(name1, name2);
4658+
}
4659+
4660+
static int
4661+
compare_readdir_item(const void *s1, const void *s2)
4662+
{
4663+
if (readdirex_sort == READDIR_SORT_BYTE)
4664+
return STRCMP(*(char **)s1, *(char **)s2);
4665+
else if (readdirex_sort == READDIR_SORT_IC)
4666+
return STRICMP(*(char **)s1, *(char **)s2);
4667+
else
4668+
return STRCOLL(*(char **)s1, *(char **)s2);
46494669
}
46504670
#endif
46514671

@@ -4663,7 +4683,8 @@ readdir_core(
46634683
char_u *path,
46644684
int withattr UNUSED,
46654685
void *context,
4666-
int (*checkitem)(void *context, void *item))
4686+
int (*checkitem)(void *context, void *item),
4687+
int sort)
46674688
{
46684689
int failed = FALSE;
46694690
char_u *p;
@@ -4687,6 +4708,8 @@ readdir_core(
46874708
else \
46884709
vim_free(item); \
46894710
} while (0)
4711+
4712+
readdirex_sort = READDIR_SORT_BYTE;
46904713
# else
46914714
# define FREE_ITEM(item) vim_free(item)
46924715
# endif
@@ -4844,15 +4867,19 @@ readdir_core(
48444867

48454868
# undef FREE_ITEM
48464869

4847-
if (!failed && gap->ga_len > 0)
4870+
if (!failed && gap->ga_len > 0 && sort > READDIR_SORT_NONE)
48484871
{
48494872
# ifdef FEAT_EVAL
4873+
readdirex_sort = sort;
48504874
if (withattr)
48514875
qsort((void*)gap->ga_data, (size_t)gap->ga_len, sizeof(dict_T*),
48524876
compare_readdirex_item);
48534877
else
4854-
# endif
4878+
qsort((void*)gap->ga_data, (size_t)gap->ga_len, sizeof(char_u *),
4879+
compare_readdir_item);
4880+
# else
48554881
sort_strings((char_u **)gap->ga_data, gap->ga_len);
4882+
# endif
48564883
}
48574884

48584885
return failed ? FAIL : OK;
@@ -4883,7 +4910,7 @@ delete_recursive(char_u *name)
48834910
exp = vim_strsave(name);
48844911
if (exp == NULL)
48854912
return -1;
4886-
if (readdir_core(&ga, exp, FALSE, NULL, NULL) == OK)
4913+
if (readdir_core(&ga, exp, FALSE, NULL, NULL, READDIR_SORT_NONE) == OK)
48874914
{
48884915
for (i = 0; i < ga.ga_len; ++i)
48894916
{

0 commit comments

Comments
 (0)