vim-patch:8.1.1120: cannot easily get directory entry matches #12222

Problem:    Cannot easily get directory entry matches.
Solution:   Add the readdir() function. (Yasuhiro Matsumoto, closes vim/vim#2439)
543c9b1921

closes #12212
This commit is contained in:
Hennadii Chernyshchyk 2020-05-05 18:15:45 +03:00 committed by GitHub
parent 48c2198297
commit d2766b06c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 153 additions and 13 deletions

View File

@ -6688,6 +6688,33 @@ range({expr} [, {max} [, {stride}]]) *range()*
range(2, -2, -1) " [2, 1, 0, -1, -2] range(2, -2, -1) " [2, 1, 0, -1, -2]
range(0) " [] range(0) " []
range(2, 0) " error! 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(".")
< <
*readfile()* *readfile()*
readfile({fname} [, {binary} [, {max}]]) readfile({fname} [, {binary} [, {max}]])
@ -6720,17 +6747,6 @@ readfile({fname} [, {binary} [, {max}]])
the result is an empty list. the result is an empty list.
Also see |writefile()|. Also see |writefile()|.
*readdir()*
readdir({directory} [, {expr}])
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.
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.
reg_executing() *reg_executing()* reg_executing() *reg_executing()*
Returns the single letter name of the register being executed. Returns the single letter name of the register being executed.
Returns an empty string when no register is being executed. Returns an empty string when no register is being executed.

View File

@ -915,7 +915,7 @@ varnumber_T eval_to_number(char_u *expr)
* Save the current typeval in "save_tv". * Save the current typeval in "save_tv".
* When not used yet add the variable to the v: hashtable. * When not used yet add the variable to the v: hashtable.
*/ */
static void prepare_vimvar(int idx, typval_T *save_tv) void prepare_vimvar(int idx, typval_T *save_tv)
{ {
*save_tv = vimvars[idx].vv_tv; *save_tv = vimvars[idx].vv_tv;
if (vimvars[idx].vv_type == VAR_UNKNOWN) if (vimvars[idx].vv_type == VAR_UNKNOWN)
@ -926,7 +926,7 @@ static void prepare_vimvar(int idx, typval_T *save_tv)
* Restore v: variable "idx" to typeval "save_tv". * Restore v: variable "idx" to typeval "save_tv".
* When no longer defined, remove the variable from the v: hashtable. * When no longer defined, remove the variable from the v: hashtable.
*/ */
static void restore_vimvar(int idx, typval_T *save_tv) void restore_vimvar(int idx, typval_T *save_tv)
{ {
hashitem_T *hi; hashitem_T *hi;

View File

@ -255,6 +255,7 @@ return {
pyeval={args=1}, pyeval={args=1},
pyxeval={args=1}, pyxeval={args=1},
range={args={1, 3}}, range={args={1, 3}},
readdir={args={1, 2}},
readfile={args={1, 3}}, readfile={args={1, 3}},
reg_executing={}, reg_executing={},
reg_recording={}, reg_recording={},

View File

@ -6316,6 +6316,99 @@ static void f_range(typval_T *argvars, typval_T *rettv, FunPtr fptr)
} }
} }
// Evaluate "expr" for readdir().
static varnumber_T readdir_checkitem(typval_T *expr, const char *name)
{
typval_T save_val;
typval_T rettv;
typval_T argv[2];
varnumber_T retval = 0;
bool 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 = (char_u *)name;
if (eval_expr_typval(expr, argv, 1, &rettv) == FAIL) {
goto theend;
}
retval = tv_get_number_chk(&rettv, &error);
if (error) {
retval = -1;
}
tv_clear(&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, FunPtr fptr)
{
typval_T *expr;
const char *path;
garray_T ga;
Directory dir;
tv_list_alloc_ret(rettv, kListLenUnknown);
path = tv_get_string(&argvars[0]);
expr = &argvars[1];
ga_init(&ga, (int)sizeof(char *), 20);
if (!os_scandir(&dir, path)) {
smsg(_(e_notopen), path);
} else {
for (;;) {
bool ignore;
path = os_scandir_next(&dir);
if (path == NULL) {
break;
}
ignore = (path[0] == '.'
&& (path[1] == NUL || (path[1] == '.' && path[2] == NUL)));
if (!ignore && expr->v_type != VAR_UNKNOWN) {
varnumber_T r = readdir_checkitem(expr, path);
if (r < 0) {
break;
}
if (r == 0) {
ignore = true;
}
}
if (!ignore) {
ga_grow(&ga, 1);
((char **)ga.ga_data)[ga.ga_len++] = xstrdup(path);
}
}
os_closedir(&dir);
}
rettv->vval.v_list = tv_list_alloc(kListLenShouldKnow);
if (rettv->vval.v_list != NULL) {
tv_list_ref(rettv->vval.v_list);
sort_strings((char_u **)ga.ga_data, ga.ga_len);
for (int i = 0; i < ga.ga_len; i++) {
path = ((const char **)ga.ga_data)[i];
tv_list_append_string(rettv->vval.v_list, path, -1);
}
}
for (int i = 0; i < ga.ga_len; i++) {
xfree(((uint8_t **)ga.ga_data)[i]);
}
ga_clear(&ga);
}
/* /*
* "readfile()" function * "readfile()" function
*/ */

View File

@ -1307,3 +1307,33 @@ func Test_bufadd_bufload()
bwipe otherName bwipe otherName
call assert_equal(0, bufexists('someName')) call assert_equal(0, bufexists('someName'))
endfunc 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