xaizek / vifm (License: GPLv2+) (since 2018-12-07)
Vifm is a file manager with curses interface, which provides Vi[m]-like environment for managing objects within file systems, extended with some useful ideas from mutt.
Commit 8e02c8d2e0e22a485cf5a6ffddc25f5d34c24f6f

Made it possible to escape commas in patterns
As usual, by doubling.

Thanks to filterfalse.
Author: xaizek
Author date (UTC): 2020-09-17 15:10
Committer name: xaizek
Committer date (UTC): 2020-09-17 15:10
Parent(s): 1bf96af13141ebf8ad2d06add48ed0d79db133ba
Signing key: 99DC5E4DB05F6BE2
Tree: ae980973d4ebc1c457a7cdf431a3226c2693564f
File Lines added Lines deleted
ChangeLog 3 0
data/man/vifm.1 4 1
data/vim/doc/app/vifm-app.txt 3 0
src/utils/globs.c 1 1
src/utils/matcher.c 31 35
tests/filetype/filetype.c 11 1
tests/utils/matcher.c 17 0
tests/utils/matchers.c 5 2
File ChangeLog changed (mode: 100644) (index 6ef8bf232..58743913a)
2 2
3 3 Recommend against setting 'shellcmdflag' to "-ic" value. Recommend against setting 'shellcmdflag' to "-ic" value.
4 4
5 Made it possible to escape commas in patterns (as usual, by doubling).
6 Thanks to filterfalse.
7
5 8 Fixed ga and gA on symlinks to directories on their own (regression in Fixed ga and gA on symlinks to directories on their own (regression in
6 9 0.11-beta) and as part of selection. Thanks to filterfalse. 0.11-beta) and as part of selection. Thanks to filterfalse.
7 10
File data/man/vifm.1 changed (mode: 100644) (index 85e21b1fa..d34416201)
1 .TH VIFM 1 "September 12, 2020" "vifm 0.11-beta"
1 .TH VIFM 1 "September 17, 2020" "vifm 0.11-beta"
2 2 .\" --------------------------------------------------------------------------- .\" ---------------------------------------------------------------------------
3 3 .SH NAME .SH NAME
4 4 .\" --------------------------------------------------------------------------- .\" ---------------------------------------------------------------------------
 
... ... character extension unless it's "d" letter. And
3629 3629 .EE .EE
3630 3630 associates `sxiv` picture viewer only for JPEG-files that contain single digit associates `sxiv` picture viewer only for JPEG-files that contain single digit
3631 3631 in their name. in their name.
3632
3633 If you need to include literal comma, which is normally separates multiple
3634 globs, double it.
3632 3635 .\" --------------------------------------------------------------------------- .\" ---------------------------------------------------------------------------
3633 3636 .SH :set options .SH :set options
3634 3637 .\" --------------------------------------------------------------------------- .\" ---------------------------------------------------------------------------
File data/vim/doc/app/vifm-app.txt changed (mode: 100644) (index 6821d0938..b8ddbd717)
... ... character extension unless it's "d" letter. And >
2990 2990 associates `sxiv` picture viewer only for JPEG-files that contain single digit associates `sxiv` picture viewer only for JPEG-files that contain single digit
2991 2991 in their name. in their name.
2992 2992
2993 If you need to include literal comma, which is normally separates multiple
2994 globs, double it.
2995
2993 2996 -------------------------------------------------------------------------------- --------------------------------------------------------------------------------
2994 2997 *vifm-set-options* *vifm-set-options*
2995 2998
File src/utils/globs.c changed (mode: 100644) (index 11e1db1b0..68efb89d2)
... ... globs_to_regex(const char globs[])
32 32
33 33 char *globs_copy = strdup(globs); char *globs_copy = strdup(globs);
34 34 char *glob = globs_copy, *state = NULL; char *glob = globs_copy, *state = NULL;
35 while((glob = split_and_get(glob, ',', &state)) != NULL)
35 while((glob = split_and_get_dc(glob, &state)) != NULL)
36 36 { {
37 37 void *p; void *p;
38 38 char *regex; char *regex;
File src/utils/matcher.c changed (mode: 100644) (index ffb351036..0670a826d)
... ... is_fglobs(char expr[])
264 264 return 0; return 0;
265 265 } }
266 266
267 int fglobs = 1;
267 expr = strdup(expr);
268 268
269 269 char *glob = expr, *state = NULL; char *glob = expr, *state = NULL;
270 while((glob = split_and_get(glob, ',', &state)) != NULL)
270 while((glob = split_and_get_dc(glob, &state)) != NULL)
271 271 { {
272 /* Need to walk to the end of the list. */
273 if(fglobs)
272 if(glob[strcspn(glob, "[?*")] == '\0')
274 273 { {
275 if(glob[strcspn(glob, "[?*")] == '\0')
276 {
277 continue;
278 }
279 if(glob[0] == '*' && glob[1 + strcspn(glob + 1, "[?*")] == '\0')
280 {
281 continue;
282 }
283 fglobs = 0;
274 continue;
284 275 } }
276 if(glob[0] == '*' && glob[1 + strcspn(glob + 1, "[?*")] == '\0')
277 {
278 continue;
279 }
280 break;
285 281 } }
286 282
287 return fglobs;
283 free(expr);
284 return (glob == NULL);
288 285 } }
289 286
290 287 /* Parses regexp flags. Returns zero on success or non-zero on error with /* Parses regexp flags. Returns zero on success or non-zero on error with
 
... ... matcher_matches(const matcher_t *matcher, const char path[])
421 418 static int static int
422 419 fglobs_matches(const matcher_t *matcher, const char path[]) fglobs_matches(const matcher_t *matcher, const char path[])
423 420 { {
424 int matched = 0;
425 char *glob = matcher->raw, *state = NULL;
426 while((glob = split_and_get(glob, ',', &state)) != NULL)
421 char *globs = strdup(matcher->raw);
422 char *glob = globs, *state = NULL;
423 while((glob = split_and_get_dc(glob, &state)) != NULL)
427 424 { {
428 /* Need to walk to the end of the list. */
429 if(!matched)
425 if(glob[strcspn(glob, "[?*")] == '\0')
430 426 { {
431 if(glob[strcspn(glob, "[?*")] == '\0')
427 if(strcasecmp(path, glob) == 0)
432 428 { {
433 matched = (strcasecmp(path, glob) == 0);
434 continue;
429 break;
435 430 } }
436 if(glob[0] == '*' && glob[1 + strcspn(glob + 1, "[?*")] == '\0')
431 }
432 if(glob[0] == '*' && glob[1 + strcspn(glob + 1, "[?*")] == '\0')
433 {
434 if(path[0] != '.' && ends_with_case(path + 1, glob + 1))
437 435 { {
438 matched = (path[0] != '.' && ends_with_case(path + 1, glob + 1));
439 continue;
436 break;
440 437 } }
441 438 } }
442 439 } }
443 return matched^matcher->negated;
440 free(globs);
441 return (glob != NULL)^matcher->negated;
444 442 } }
445 443
446 444 int int
 
... ... static int
487 485 fglobs_includes(const matcher_t *matcher, const matcher_t *like) fglobs_includes(const matcher_t *matcher, const matcher_t *like)
488 486 { {
489 487 char *like_globs_copy = strdup(like->raw); char *like_globs_copy = strdup(like->raw);
490 char *mglobs_copy = strdup(matcher->raw);
491 488
492 489 int all_matched = 1; int all_matched = 1;
493 490 char *like_glob = like_globs_copy, *like_state = NULL; char *like_glob = like_globs_copy, *like_state = NULL;
494 while((like_glob = split_and_get(like_glob, ',', &like_state)) != NULL)
491 while((like_glob = split_and_get_dc(like_glob, &like_state)) != NULL)
495 492 { {
496 int matched = 0;
493 char *mglobs_copy = strdup(matcher->raw);
497 494 char *mglob = mglobs_copy, *mstate = NULL; char *mglob = mglobs_copy, *mstate = NULL;
498 while((mglob = split_and_get(mglob, ',', &mstate)) != NULL)
495 while((mglob = split_and_get_dc(mglob, &mstate)) != NULL)
499 496 { {
500 /* Need to walk to the end of the list. */
501 if(!matched && strcasecmp(like_glob, mglob) == 0)
497 if(strcasecmp(like_glob, mglob) == 0)
502 498 { {
503 matched = 1;
499 break;
504 500 } }
505 501 } }
502 free(mglobs_copy);
506 503
507 if(!matched)
504 if(mglob == NULL)
508 505 { {
509 506 all_matched = 0; all_matched = 0;
510 507 break; break;
 
... ... fglobs_includes(const matcher_t *matcher, const matcher_t *like)
512 509 } }
513 510
514 511 free(like_globs_copy); free(like_globs_copy);
515 free(mglobs_copy);
516 512 return all_matched; return all_matched;
517 513 } }
518 514
File tests/filetype/filetype.c changed (mode: 100644) (index 6e2e50e31..6bb5680d2)
... ... TEST(star_and_dot)
124 124 assert_string_equal("hlibreoffice", prog_cmd); assert_string_equal("hlibreoffice", prog_cmd);
125 125 } }
126 126
127 TEST(double_comma)
127 TEST(double_comma_in_command)
128 128 { {
129 129 const char *prog_cmd; const char *prog_cmd;
130 130
 
... ... TEST(double_comma)
139 139 assert_string_equal("prog1 -o opt1", prog_cmd); assert_string_equal("prog1 -o opt1", prog_cmd);
140 140 } }
141 141
142 TEST(double_comma_in_pattern)
143 {
144 set_programs("a,,b,*.tar", "prog1", 0, 0);
145 assert_string_equal("prog1", ft_get_program("a,b"));
146 assert_string_equal("prog1", ft_get_program("file.version.tar"));
147
148 set_programs("{c,,d}", "prog2", 0, 0);
149 assert_string_equal("prog2", ft_get_program("c,d"));
150 }
151
142 152 TEST(zero_length_match) TEST(zero_length_match)
143 153 { {
144 154 /* This is confirmation of limitation of globs->regex transition. There is a /* This is confirmation of limitation of globs->regex transition. There is a
File tests/utils/matcher.c changed (mode: 100644) (index 4af56c54f..6ca4417ae)
... ... TEST(regexps_are_cloned)
354 354 matcher_free(clone); matcher_free(clone);
355 355 } }
356 356
357 TEST(comma_escaping)
358 {
359 char *error;
360 matcher_t *m;
361
362 assert_non_null(m = matcher_alloc("{a,,b,*.ext,c,,d}", 0, 1, "", &error));
363 assert_null(error);
364
365 check_glob(m);
366 assert_false(matcher_matches(m, "a"));
367 assert_false(matcher_matches(m, "b"));
368 assert_true(matcher_matches(m, "a,b"));
369 assert_true(matcher_matches(m, "c,d"));
370
371 matcher_free(m);
372 }
373
357 374 TEST(mime_type_pattern, IF(has_mime_type_detection)) TEST(mime_type_pattern, IF(has_mime_type_detection))
358 375 { {
359 376 char *error; char *error;
File tests/utils/matchers.c changed (mode: 100644) (index ef6a16e61..8d886da43)
... ... TEST(inclusion_check_works)
247 247 TEST(breaking_list_of_lists) TEST(breaking_list_of_lists)
248 248 { {
249 249 int nmatchers; int nmatchers;
250 char **const matchers = matchers_list("<mime>{*.,ext},/re,gex/", &nmatchers);
251 if(nmatchers == 2)
250 char **const matchers =
251 matchers_list("<mime>{*.,ext},/re,gex/,{a,,b,c,,d},{{x,,y}}", &nmatchers);
252 if(nmatchers == 4)
252 253 { {
253 254 assert_string_equal("<mime>{*.,ext}", matchers[0]); assert_string_equal("<mime>{*.,ext}", matchers[0]);
254 255 assert_string_equal("/re,gex/", matchers[1]); assert_string_equal("/re,gex/", matchers[1]);
256 assert_string_equal("{a,,b,c,,d}", matchers[2]);
257 assert_string_equal("{{x,,y}}", matchers[3]);
255 258 } }
256 259 free_string_array(matchers, nmatchers); free_string_array(matchers, nmatchers);
257 260 } }
Hints

Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://code.reversed.top/user/xaizek/vifm

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@code.reversed.top/user/xaizek/vifm

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a pull request:
... clone the repository ...
... make some changes and some commits ...
git push origin master