Skip to content
Permalink
Browse files

patch 8.1.1120: cannot easily get directory entry matches

Problem:    Cannot easily get directory entry matches.
Solution:   Add the readdir() function. (Yasuhiro Matsumoto, closes #2439)
  • Loading branch information...
brammool committed Apr 5, 2019
1 parent 577fadf commit 543c9b1921d7605498b54afdef518e312f1b4515
Showing with 256 additions and 3 deletions.
  1. +30 −0 runtime/doc/eval.txt
  2. +3 −3 src/eval.c
  3. +185 −0 src/evalfunc.c
  4. +3 −0 src/misc1.c
  5. +3 −0 src/proto/eval.pro
  6. +30 −0 src/testdir/test_functions.vim
  7. +2 −0 src/version.c
@@ -2507,6 +2507,9 @@ py3eval({expr}) any evaluate |python3| expression
pyxeval({expr}) any evaluate |python_x| expression
range({expr} [, {max} [, {stride}]])
List items from {expr} to {max}
readdir({directory} [, {expr}])
List file names on {dir} with evalating
{expr}
readfile({fname} [, {type} [, {max}]])
List get list of lines from file {fname}
reg_executing() String get the executing register name
@@ -7255,6 +7258,33 @@ range({expr} [, {max} [, {stride}]]) *range()*
range(2, -2, -1) " [2, 1, 0, -1, -2]
range(0) " []
range(2, 0) " error!
<
*readdir()*
readdir({directory} [, {expr}])
Return a list with file and directory names in {directory}.

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.
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": >
readdir(dirname, {n -> n =~ '.txt$'})
< To skip hidden and backup files: >
readdir(dirname, {n -> n !~ '^\.\|\~$'})

< If you want to get a directory tree: >
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(".")

This comment has been minimized.

Copy link
@chdiza

chdiza Apr 5, 2019

How about adding some description in this help text about what readdir() does that can't already be done with glob()? The function seems completely pointless otherwise.

This comment has been minimized.

Copy link
@brammool

brammool via email Apr 6, 2019

Author Contributor

This comment has been minimized.

Copy link
@chdiza

chdiza Apr 6, 2019

That's better than nothing, but I'm still wondering what the point of readdir() is. If I want to do "complicated" things, I'll just save the glob() results and then operate upon them with more Vimscript, e.g., filter(). (Which is what we users have been doing for years.) glob() can even directly give you a list (instead of a string that must be split()).

Vimscript seems to be really bloating during the last year or so.

This comment has been minimized.

Copy link
@brammool

brammool via email Apr 6, 2019

Author Contributor

This comment has been minimized.

Copy link
@chdiza

chdiza Apr 6, 2019

But then you possibly already have gathered thousands of matches, instead of bailing out after the ten you wanted.

That would be a good thing to mention in the help, and should probably be one of the examples. (I do wonder, though, how that could really be enough of a performance boost to be worth it.)

E.g. for completion.

I thought completion has its own special code and uses neither glob() nor readdir().

This comment has been minimized.

Copy link
@brammool

brammool via email Apr 7, 2019

Author Contributor

This comment has been minimized.

Copy link
@mattn

mattn Apr 7, 2019

Also glob can not read files matched to wildignore and suffixes. We will have to fix delete() too since delete() is implemented with glob().

This comment has been minimized.

Copy link
@chdiza

chdiza Apr 7, 2019

Also glob can not read files matched to wildignore and suffixes.

Yes it can. That's what the {nosuf} optional argument to glob() is for.

This comment has been minimized.

Copy link
@k-takata

k-takata Apr 7, 2019

Member

This might be useful for fixing the issue #696.

<
*readfile()*
readfile({fname} [, {type} [, {max}]])
@@ -753,7 +753,7 @@ eval1_emsg(char_u **arg, typval_T *rettv, int evaluate)
return ret;
}

static int
int
eval_expr_typval(typval_T *expr, typval_T *argv, int argc, typval_T *rettv)
{
char_u *s;
@@ -966,7 +966,7 @@ eval_to_number(char_u *expr)
* Save the current typeval in "save_tv".
* When not used yet add the variable to the v: hashtable.
*/
static void
void
prepare_vimvar(int idx, typval_T *save_tv)
{
*save_tv = vimvars[idx].vv_tv;
@@ -978,7 +978,7 @@ prepare_vimvar(int idx, typval_T *save_tv)
* Restore v: variable "idx" to typeval "save_tv".
* When no longer defined, remove the variable from the v: hashtable.
*/
static void
void
restore_vimvar(int idx, typval_T *save_tv)
{
hashitem_T *hi;
@@ -319,6 +319,7 @@ static void f_pyeval(typval_T *argvars, typval_T *rettv);
static void f_pyxeval(typval_T *argvars, typval_T *rettv);
#endif
static void f_range(typval_T *argvars, typval_T *rettv);
static void f_readdir(typval_T *argvars, typval_T *rettv);
static void f_readfile(typval_T *argvars, typval_T *rettv);
static void f_reg_executing(typval_T *argvars, typval_T *rettv);
static void f_reg_recording(typval_T *argvars, typval_T *rettv);
@@ -819,6 +820,7 @@ static struct fst
{"pyxeval", 1, 1, f_pyxeval},
#endif
{"range", 1, 3, f_range},
{"readdir", 1, 2, f_readdir},
{"readfile", 1, 3, f_readfile},
{"reg_executing", 0, 0, f_reg_executing},
{"reg_recording", 0, 0, f_reg_recording},
@@ -9117,6 +9119,189 @@ f_range(typval_T *argvars, typval_T *rettv)
}
}

/*
* Evaluate "expr" for readdir().
*/
static int
readdir_checkitem(typval_T *expr, char_u *name)
{
typval_T save_val;
typval_T rettv;
typval_T argv[2];
int retval = 0;
int error = FALSE;

prepare_vimvar(VV_VAL, &save_val);
set_vim_var_string(VV_VAL, name, -1);
argv[0].v_type = VAR_STRING;
argv[0].vval.v_string = name;

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_string(VV_VAL, NULL, 0);
restore_vimvar(VV_VAL, &save_val);
return retval;
}

/*
* "readdir()" function
*/
static void
f_readdir(typval_T *argvars, typval_T *rettv)
{
typval_T *expr;
int failed = FALSE;
char_u *path;
garray_T ga;
int i;
#ifdef MSWIN
char_u *buf, *p;
WIN32_FIND_DATA fb;
int ok;
HANDLE hFind = INVALID_HANDLE_VALUE;
WIN32_FIND_DATAW wfb;
WCHAR *wn = NULL; // UCS-2 name, NULL when not used.
#endif

if (rettv_list_alloc(rettv) == FAIL)
return;
path = tv_get_string(&argvars[0]);
expr = &argvars[1];
ga_init2(&ga, (int)sizeof(char *), 20);

#ifdef MSWIN
buf = alloc((int)MAXPATHL);
if (buf == NULL)
return;
STRNCPY(buf, path, MAXPATHL-5);
p = vim_strpbrk(path, (char_u *)"\\/");
if (p != NULL)
*p = NUL;
STRCAT(buf, "\\*");

wn = enc_to_utf16(buf, NULL);
if (wn != NULL)
hFind = FindFirstFileW(wn, &wfb);
ok = (hFind != INVALID_HANDLE_VALUE);
if (!ok)
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 && expr->v_type != VAR_UNKNOWN)
{
int r = readdir_checkitem(expr, p);

if (r < 0)
{
vim_free(p);
break;
}
if (r == 0)
ignore = TRUE;
}

if (!ignore)
{
if (ga_grow(&ga, 1) == OK)
((char_u**)ga.ga_data)[ga.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;
char_u *p;

dirp = opendir((char *)path);
if (dirp == NULL)
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 && expr->v_type != VAR_UNKNOWN)
{
int r = readdir_checkitem(expr, p);

if (r < 0)
break;
if (r == 0)
ignore = TRUE;
}

if (!ignore)
{
if (ga_grow(&ga, 1) == OK)
((char_u**)ga.ga_data)[ga.ga_len++] = vim_strsave(p);
else
{
failed = TRUE;
break;
}
}
}

closedir(dirp);
}
#endif

rettv->vval.v_list = list_alloc();
if (!failed && rettv->vval.v_list != NULL)
{
++rettv->vval.v_list->lv_refcount;
sort_strings((char_u **)ga.ga_data, ga.ga_len);
for (i = 0; i < ga.ga_len; i++)
{
p = ((char_u **)ga.ga_data)[i];
list_append_string(rettv->vval.v_list, p, -1);
}
}
for (i = 0; i < ga.ga_len; i++)
vim_free(((char_u **)ga.ga_data)[i]);

ga_clear(&ga);
}

/*
* "readfile()" function
*/
@@ -5790,6 +5790,9 @@ dos_expandpath(
while (ok)
{
p = utf16_to_enc(wfb.cFileName, NULL); // p is allocated here
if (p == NULL)
break; // out of memory

// Ignore entries starting with a dot, unless when asked for. Accept
// all entries found with "matchname".
if ((p[0] != '.' || starts_with_dot
@@ -10,12 +10,15 @@ int eval_printexpr(char_u *fname, char_u *args);
void eval_diff(char_u *origfile, char_u *newfile, char_u *outfile);
void eval_patch(char_u *origfile, char_u *difffile, char_u *outfile);
int eval_to_bool(char_u *arg, int *error, char_u **nextcmd, int skip);
int eval_expr_typval(typval_T *expr, typval_T *argv, int argc, typval_T *rettv);
int eval_expr_to_bool(typval_T *expr, int *error);
char_u *eval_to_string_skip(char_u *arg, char_u **nextcmd, int skip);
int skip_expr(char_u **pp);
char_u *eval_to_string(char_u *arg, char_u **nextcmd, int convert);
char_u *eval_to_string_safe(char_u *arg, char_u **nextcmd, int use_sandbox);
varnumber_T eval_to_number(char_u *expr);
void prepare_vimvar(int idx, typval_T *save_tv);
void restore_vimvar(int idx, typval_T *save_tv);
list_T *eval_spell_expr(char_u *badword, char_u *expr);
int get_spellword(list_T *list, char_u **pp);
typval_T *eval_expr(char_u *arg, char_u **nextcmd);
@@ -1401,3 +1401,33 @@ func Test_platform_name()
call assert_equal(uname =~? 'CYGWIN\|MSYS', has('win32unix'))
endif
endfunc

func Test_readdir()
call mkdir('Xdir')
call writefile([], 'Xdir/foo.txt')
call writefile([], 'Xdir/bar.txt')
call mkdir('Xdir/dir')

" All results
let files = readdir('Xdir')
call assert_equal(['bar.txt', 'dir', 'foo.txt'], sort(files))

" Only results containing "f"
let files = readdir('Xdir', { x -> stridx(x, 'f') !=- 1 })
call assert_equal(['foo.txt'], sort(files))

" Only .txt files
let files = readdir('Xdir', { x -> x =~ '.txt$' })
call assert_equal(['bar.txt', 'foo.txt'], sort(files))

" Only .txt files with string
let files = readdir('Xdir', 'v:val =~ ".txt$"')
call assert_equal(['bar.txt', 'foo.txt'], sort(files))

" Limit to 1 result.
let l = []
let files = readdir('Xdir', {x -> len(add(l, x)) == 2 ? -1 : 1})
call assert_equal(1, len(files))

call delete('Xdir', 'rf')
endfunc
@@ -771,6 +771,8 @@ static char *(features[]) =

static int included_patches[] =
{ /* Add new patch number below this line */
/**/
1120,
/**/
1119,
/**/

0 comments on commit 543c9b1

Please sign in to comment.
You can’t perform that action at this time.