Problem: Getting directory contents is always case sorted.
Solution: Add sort options and v:collate. (Christian Brabandt, closes #6229)
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
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}])
: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:
< 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}.
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:
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()
: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.
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
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
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 :
{
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);
#undef HAVE_SIGVEC
#undef HAVE_SMACK
#undef HAVE_STRCASECMP
+#undef HAVE_STRCOLL
#undef HAVE_STRERROR
#undef HAVE_STRFTIME
#undef HAVE_STRICMP
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
},
{"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},
{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
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
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)
// 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
}
# 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
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];
}
/*
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,
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
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;
else \
vim_free(item); \
} while (0)
+
+ readdirex_sort = READDIR_SORT_BYTE;
# else
# define FREE_ITEM(item) vim_free(item)
# endif
# 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;
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)
{
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
*/
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++)
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++)
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"));
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);
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 . '\>', @:)
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()
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')
static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 988,
/**/
987,
/**/
# 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)
#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
#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
#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