diff -Nru fzy-0.9/ALGORITHM.md fzy-1.0/ALGORITHM.md --- fzy-0.9/ALGORITHM.md 2017-04-17 07:55:42.000000000 +0000 +++ fzy-1.0/ALGORITHM.md 2018-09-23 22:05:55.000000000 +0000 @@ -80,7 +80,7 @@ ## TextMate TextMate deserves immense credit for popularizing fuzzy finding from inside -text editors. It's influence can be found in the commant-t project, various +text editors. It's influence can be found in the command-t project, various other editors use command-t for file finding, and the 't' command in the github web interface. @@ -101,7 +101,7 @@ ## Length of shortest first match: fzf https://github.com/junegunn/fzf/blob/master/src/algo/algo.go -Fzy scores based on the size of the greedy shortest match. fzf finds it's match +Fzy scores based on the size of the greedy shortest match. fzf finds its match by the first match appearing in the candidate string. It has some cleverness to find if there is a shorter match contained in that search, but it isn't guaranteed to find the shortest match in the string. @@ -137,7 +137,7 @@ * x**ABXC**x * x**ABXC**xbc -The third result here shoud have been scored the same as the first, but the +The third result here should have been scored the same as the first, but the lower scoring but shorter match is what is measured. @@ -150,11 +150,6 @@ # Possible fzy Algorithm Improvements -## Multithreading - -Currently a single thread is used for finding matches. Using multiple threads -would likely be faster, but require some additional complexity. - ## Case sensitivity fzy currently treats all searches as case-insensitive. However, scoring prefers diff -Nru fzy-0.9/CHANGELOG.md fzy-1.0/CHANGELOG.md --- fzy-0.9/CHANGELOG.md 2017-04-17 07:55:42.000000000 +0000 +++ fzy-1.0/CHANGELOG.md 2018-09-23 22:05:55.000000000 +0000 @@ -1,3 +1,17 @@ +## 1.0 (2018-09-23) + +Features: + + - Support UTF-8 + - Support readline-like editing + - Quit on Esc + - Redraw on terminal resize + - Bracketed paste escapes are ignored + +Performance: + + - Initialize tty interface before reading stdin + ## 0.9 (2017-04-17) Features: diff -Nru fzy-0.9/debian/changelog fzy-1.0/debian/changelog --- fzy-0.9/debian/changelog 2017-04-20 01:38:07.000000000 +0000 +++ fzy-1.0/debian/changelog 2019-01-27 11:20:44.000000000 +0000 @@ -1,3 +1,15 @@ +fzy (1.0-1) unstable; urgency=medium + + [ Ondřej Nový ] + * d/control: Set Vcs-* to salsa.debian.org + + [ ChangZhuo Chen (陳昌倬) ] + * New upstream release. + * Bump Standards-Version to 4.3.0. + * Bump compat to 11. + + -- ChangZhuo Chen (陳昌倬) Sun, 27 Jan 2019 19:20:44 +0800 + fzy (0.9-1) unstable; urgency=medium * New upstream release (Closes: #860207). diff -Nru fzy-0.9/debian/compat fzy-1.0/debian/compat --- fzy-0.9/debian/compat 2017-04-10 09:01:42.000000000 +0000 +++ fzy-1.0/debian/compat 2019-01-27 11:14:16.000000000 +0000 @@ -1 +1 @@ -10 +11 diff -Nru fzy-0.9/debian/control fzy-1.0/debian/control --- fzy-0.9/debian/control 2017-04-10 10:28:44.000000000 +0000 +++ fzy-1.0/debian/control 2019-01-27 11:20:24.000000000 +0000 @@ -2,11 +2,11 @@ Section: utils Priority: optional Maintainer: ChangZhuo Chen (陳昌倬) -Build-Depends: debhelper (>= 10) -Standards-Version: 3.9.8 +Build-Depends: debhelper (>= 11~) +Standards-Version: 4.3.0 Homepage: https://github.com/jhawthorn/fzy -Vcs-Git: https://anonscm.debian.org/collab-maint/fzy.git -Vcs-Browser: https://anonscm.debian.org/cgit/collab-maint/fzy.git +Vcs-Git: https://salsa.debian.org/debian/fzy.git +Vcs-Browser: https://salsa.debian.org/debian/fzy Package: fzy Architecture: any diff -Nru fzy-0.9/deps/greatest/greatest.h fzy-1.0/deps/greatest/greatest.h --- fzy-0.9/deps/greatest/greatest.h 2017-04-17 07:55:42.000000000 +0000 +++ fzy-1.0/deps/greatest/greatest.h 2018-09-23 22:05:55.000000000 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2016 Scott Vokes + * Copyright (c) 2011-2017 Scott Vokes * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -17,13 +17,13 @@ #ifndef GREATEST_H #define GREATEST_H -#ifdef __cplusplus +#if defined(__cplusplus) && !defined(GREATEST_NO_EXTERN_CPLUSPLUS) extern "C" { #endif -/* 1.2.1 */ +/* 1.3.1 */ #define GREATEST_VERSION_MAJOR 1 -#define GREATEST_VERSION_MINOR 2 +#define GREATEST_VERSION_MINOR 3 #define GREATEST_VERSION_PATCH 1 /* A unit testing system for C, contained in 1 file. @@ -122,6 +122,12 @@ #define GREATEST_USE_LONGJMP 1 #endif +/* Make it possible to replace fprintf with another + * function with the same interface. */ +#ifndef GREATEST_FPRINTF +#define GREATEST_FPRINTF fprintf +#endif + #if GREATEST_USE_LONGJMP #include #endif @@ -141,6 +147,7 @@ #define GREATEST_FLOAT_FMT "%g" #endif + /********* * Types * *********/ @@ -162,12 +169,12 @@ } greatest_suite_info; /* Type for a suite function. */ -typedef void (greatest_suite_cb)(void); +typedef void greatest_suite_cb(void); /* Types for setup/teardown callbacks. If non-NULL, these will be run * and passed the pointer to their additional data. */ -typedef void (greatest_setup_cb)(void *udata); -typedef void (greatest_teardown_cb)(void *udata); +typedef void greatest_setup_cb(void *udata); +typedef void greatest_teardown_cb(void *udata); /* Type for an equality comparison between two pointers of the same type. * Should return non-0 if equal, otherwise 0. @@ -201,24 +208,41 @@ GREATEST_FLAG_LIST_ONLY = 0x02 } greatest_flag_t; +/* Internal state for a PRNG, used to shuffle test order. */ +struct greatest_prng { + unsigned char random_order; /* use random ordering? */ + unsigned char initialized; /* is random ordering initialized? */ + unsigned char pad_0[2]; + unsigned long state; /* PRNG state */ + unsigned long count; /* how many tests, this pass */ + unsigned long count_ceil; /* total number of tests */ + unsigned long count_run; /* total tests run */ + unsigned long mod; /* power-of-2 ceiling of count_ceil */ + unsigned long a; /* LCG multiplier */ + unsigned long c; /* LCG increment */ +}; + /* Struct containing all test runner state. */ typedef struct greatest_run_info { unsigned char flags; unsigned char verbosity; + unsigned char pad_0[2]; + unsigned int tests_run; /* total test count */ + /* currently running test suite */ + greatest_suite_info suite; + /* overall pass/fail/skip counts */ unsigned int passed; unsigned int failed; unsigned int skipped; unsigned int assertions; - /* currently running test suite */ - greatest_suite_info suite; - /* info to print about the most recent failure */ - const char *fail_file; unsigned int fail_line; + unsigned int pad_1; + const char *fail_file; const char *msg; /* current setup/teardown hooks and userdata */ @@ -234,6 +258,9 @@ /* only run a specific suite or test */ const char *suite_filter; const char *test_filter; + const char *test_exclude; + + struct greatest_prng prng[2]; /* 0: suites, 1: tests */ #if GREATEST_USE_TIME /* overall timers */ @@ -242,6 +269,7 @@ #endif #if GREATEST_USE_LONGJMP + int pad_jmp_buf; jmp_buf jump_dest; #endif } greatest_run_info; @@ -269,18 +297,25 @@ void greatest_do_pass(const char *name); void greatest_do_fail(const char *name); void greatest_do_skip(const char *name); -int greatest_pre_test(const char *name); -void greatest_post_test(const char *name, int res); +int greatest_suite_pre(const char *suite_name); +void greatest_suite_post(void); +int greatest_test_pre(const char *name); +void greatest_test_post(const char *name, int res); void greatest_usage(const char *name); int greatest_do_assert_equal_t(const void *exp, const void *got, - greatest_type_info *type_info, void *udata); +greatest_type_info *type_info, void *udata); +void greatest_prng_init_first_pass(int id); +int greatest_prng_init_second_pass(int id, unsigned long seed); +void greatest_prng_step(int id); /* These are part of the public greatest API. */ void GREATEST_SET_SETUP_CB(greatest_setup_cb *cb, void *udata); void GREATEST_SET_TEARDOWN_CB(greatest_teardown_cb *cb, void *udata); int greatest_all_passed(void); -void greatest_set_test_filter(const char *name); -void greatest_set_suite_filter(const char *name); +void greatest_set_suite_filter(const char *filter); +void greatest_set_test_filter(const char *filter); +void greatest_set_test_exclude(const char *filter); +void greatest_stop_at_first_fail(void); void greatest_get_report(struct greatest_report_t *report); unsigned int greatest_get_verbosity(void); void greatest_set_verbosity(unsigned int verbosity); @@ -325,14 +360,12 @@ /* Run a test in the current suite. */ #define GREATEST_RUN_TEST(TEST) \ do { \ - if (greatest_pre_test(#TEST) == 1) { \ + if (greatest_test_pre(#TEST) == 1) { \ enum greatest_test_res res = GREATEST_SAVE_CONTEXT(); \ if (res == GREATEST_TEST_RES_PASS) { \ res = TEST(); \ } \ - greatest_post_test(#TEST, res); \ - } else if (GREATEST_LIST_ONLY()) { \ - fprintf(GREATEST_STDOUT, " %s\n", #TEST); \ + greatest_test_post(#TEST, res); \ } \ } while (0) @@ -343,22 +376,24 @@ * which can be a pointer to a struct with multiple arguments. */ #define GREATEST_RUN_TEST1(TEST, ENV) \ do { \ - if (greatest_pre_test(#TEST) == 1) { \ - int res = TEST(ENV); \ - greatest_post_test(#TEST, res); \ - } else if (GREATEST_LIST_ONLY()) { \ - fprintf(GREATEST_STDOUT, " %s\n", #TEST); \ + if (greatest_test_pre(#TEST) == 1) { \ + enum greatest_test_res res = GREATEST_SAVE_CONTEXT(); \ + if (res == GREATEST_TEST_RES_PASS) { \ + res = TEST(ENV); \ + } \ + greatest_test_post(#TEST, res); \ } \ } while (0) #ifdef GREATEST_VA_ARGS #define GREATEST_RUN_TESTp(TEST, ...) \ do { \ - if (greatest_pre_test(#TEST) == 1) { \ - int res = TEST(__VA_ARGS__); \ - greatest_post_test(#TEST, res); \ - } else if (GREATEST_LIST_ONLY()) { \ - fprintf(GREATEST_STDOUT, " %s\n", #TEST); \ + if (greatest_test_pre(#TEST) == 1) { \ + enum greatest_test_res res = GREATEST_SAVE_CONTEXT(); \ + if (res == GREATEST_TEST_RES_PASS) { \ + res = TEST(__VA_ARGS__); \ + } \ + greatest_test_post(#TEST, res); \ } \ } while (0) #endif @@ -371,7 +406,8 @@ #define GREATEST_FIRST_FAIL() \ (greatest_info.flags & GREATEST_FLAG_FIRST_FAIL) #define GREATEST_FAILURE_ABORT() \ - (greatest_info.suite.failed > 0 && GREATEST_FIRST_FAIL()) + (GREATEST_FIRST_FAIL() && \ + (greatest_info.suite.failed > 0 || greatest_info.failed > 0)) /* Message-less forms of tests defined below. */ #define GREATEST_PASS() GREATEST_PASSm(NULL) @@ -432,17 +468,17 @@ } while (0) /* Fail if EXP != GOT (equality comparison by ==). - * Warning: EXP and GOT will be evaluated more than once on failure. */ + * Warning: FMT, EXP, and GOT will be evaluated more + * than once on failure. */ #define GREATEST_ASSERT_EQ_FMTm(MSG, EXP, GOT, FMT) \ do { \ - const char *greatest_FMT = ( FMT ); \ greatest_info.assertions++; \ if ((EXP) != (GOT)) { \ - fprintf(GREATEST_STDOUT, "\nExpected: "); \ - fprintf(GREATEST_STDOUT, greatest_FMT, EXP); \ - fprintf(GREATEST_STDOUT, "\n Got: "); \ - fprintf(GREATEST_STDOUT, greatest_FMT, GOT); \ - fprintf(GREATEST_STDOUT, "\n"); \ + GREATEST_FPRINTF(GREATEST_STDOUT, "\nExpected: "); \ + GREATEST_FPRINTF(GREATEST_STDOUT, FMT, EXP); \ + GREATEST_FPRINTF(GREATEST_STDOUT, "\n Got: "); \ + GREATEST_FPRINTF(GREATEST_STDOUT, FMT, GOT); \ + GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ GREATEST_FAILm(MSG); \ } \ } while (0) @@ -454,9 +490,9 @@ int greatest_GOT = (int)(GOT); \ greatest_enum_str_fun *greatest_ENUM_STR = ENUM_STR; \ if (greatest_EXP != greatest_GOT) { \ - fprintf(GREATEST_STDOUT, "\nExpected: %s", \ + GREATEST_FPRINTF(GREATEST_STDOUT, "\nExpected: %s", \ greatest_ENUM_STR(greatest_EXP)); \ - fprintf(GREATEST_STDOUT, "\n Got: %s\n", \ + GREATEST_FPRINTF(GREATEST_STDOUT, "\n Got: %s\n", \ greatest_ENUM_STR(greatest_GOT)); \ GREATEST_FAILm(MSG); \ } \ @@ -473,7 +509,7 @@ greatest_EXP - greatest_GOT > greatest_TOL) || \ (greatest_EXP < greatest_GOT && \ greatest_GOT - greatest_EXP > greatest_TOL)) { \ - fprintf(GREATEST_STDOUT, \ + GREATEST_FPRINTF(GREATEST_STDOUT, \ "\nExpected: " GREATEST_FLOAT_FMT \ " +/- " GREATEST_FLOAT_FMT \ "\n Got: " GREATEST_FLOAT_FMT \ @@ -574,13 +610,13 @@ #define GREATEST_SET_TIME(NAME) \ NAME = clock(); \ if (NAME == (clock_t) -1) { \ - fprintf(GREATEST_STDOUT, \ + GREATEST_FPRINTF(GREATEST_STDOUT, \ "clock error: %s\n", #NAME); \ exit(EXIT_FAILURE); \ } #define GREATEST_CLOCK_DIFF(C1, C2) \ - fprintf(GREATEST_STDOUT, " (%lu ticks, %.3f sec)", \ + GREATEST_FPRINTF(GREATEST_STDOUT, " (%lu ticks, %.3f sec)", \ (long unsigned int) (C2) - (long unsigned int)(C1), \ (double)((C2) - (C1)) / (1.0 * (double)CLOCKS_PER_SEC)) #else @@ -590,8 +626,8 @@ #if GREATEST_USE_LONGJMP #define GREATEST_SAVE_CONTEXT() \ - /* setjmp returns 0 (GREATEST_TEST_RES_PASS) on first call */ \ - /* so the test runs, then RES_FAIL from FAIL_WITH_LONGJMP. */ \ + /* setjmp returns 0 (GREATEST_TEST_RES_PASS) on first call * \ + * so the test runs, then RES_FAIL from FAIL_WITH_LONGJMP. */ \ ((enum greatest_test_res)(setjmp(greatest_info.jump_dest))) #else #define GREATEST_SAVE_CONTEXT() \ @@ -599,14 +635,46 @@ GREATEST_TEST_RES_PASS #endif +/* Run every suite / test function run within BODY in pseudo-random + * order, seeded by SEED. (The top 3 bits of the seed are ignored.) + * + * This should be called like: + * GREATEST_SHUFFLE_TESTS(seed, { + * GREATEST_RUN_TEST(some_test); + * GREATEST_RUN_TEST(some_other_test); + * GREATEST_RUN_TEST(yet_another_test); + * }); + * + * Note that the body of the second argument will be evaluated + * multiple times. */ +#define GREATEST_SHUFFLE_SUITES(SD, BODY) GREATEST_SHUFFLE(0, SD, BODY) +#define GREATEST_SHUFFLE_TESTS(SD, BODY) GREATEST_SHUFFLE(1, SD, BODY) +#define GREATEST_SHUFFLE(ID, SD, BODY) \ + do { \ + struct greatest_prng *prng = &greatest_info.prng[ID]; \ + greatest_prng_init_first_pass(ID); \ + do { \ + prng->count = 0; \ + if (prng->initialized) { greatest_prng_step(ID); } \ + BODY; \ + if (!prng->initialized) { \ + if (!greatest_prng_init_second_pass(ID, SD)) { break; } \ + } else if (prng->count_run == prng->count_ceil) { \ + break; \ + } \ + } while (!GREATEST_FAILURE_ABORT()); \ + prng->count_run = prng->random_order = prng->initialized = 0; \ + } while(0) + /* Include several function definitions in the main test file. */ #define GREATEST_MAIN_DEFS() \ \ /* Is FILTER a subset of NAME? */ \ -static int greatest_name_match(const char *name, \ - const char *filter) { \ +static int greatest_name_match(const char *name, const char *filter, \ + int res_if_none) { \ size_t offset = 0; \ - size_t filter_len = strlen(filter); \ + size_t filter_len = filter ? strlen(filter) : 0; \ + if (filter_len == 0) { return res_if_none; } /* no filter */ \ while (name[offset] != '\0') { \ if (name[offset] == filter[0]) { \ if (0 == strncmp(&name[offset], filter, filter_len)) { \ @@ -619,22 +687,34 @@ return 0; \ } \ \ -int greatest_pre_test(const char *name) { \ - if (!GREATEST_LIST_ONLY() \ - && (!GREATEST_FIRST_FAIL() || greatest_info.suite.failed == 0) \ - && (greatest_info.test_filter == NULL || \ - greatest_name_match(name, greatest_info.test_filter))) { \ - GREATEST_SET_TIME(greatest_info.suite.pre_test); \ - if (greatest_info.setup) { \ - greatest_info.setup(greatest_info.setup_udata); \ +/* Before running a test, check the name filtering and \ + * test shuffling state, if applicable, and then call setup hooks. */ \ +int greatest_test_pre(const char *name) { \ + struct greatest_run_info *g = &greatest_info; \ + int match = greatest_name_match(name, g->test_filter, 1) && \ + !greatest_name_match(name, g->test_exclude, 0); \ + if (GREATEST_LIST_ONLY()) { /* just listing test names */ \ + if (match) { fprintf(GREATEST_STDOUT, " %s\n", name); } \ + return 0; \ + } \ + if (match && (!GREATEST_FIRST_FAIL() || g->suite.failed == 0)) { \ + struct greatest_prng *p = &g->prng[1]; \ + if (p->random_order) { \ + p->count++; \ + if (!p->initialized || ((p->count - 1) != p->state)) { \ + return 0; /* don't run this test yet */ \ + } \ } \ + GREATEST_SET_TIME(g->suite.pre_test); \ + if (g->setup) { g->setup(g->setup_udata); } \ + p->count_run++; \ return 1; /* test should be run */ \ } else { \ return 0; /* skipped */ \ } \ } \ \ -void greatest_post_test(const char *name, int res) { \ +void greatest_test_post(const char *name, int res) { \ GREATEST_SET_TIME(greatest_info.suite.post_test); \ if (greatest_info.teardown) { \ void *udata = greatest_info.teardown_udata; \ @@ -653,17 +733,17 @@ if (GREATEST_IS_VERBOSE()) { \ GREATEST_CLOCK_DIFF(greatest_info.suite.pre_test, \ greatest_info.suite.post_test); \ - fprintf(GREATEST_STDOUT, "\n"); \ + GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ } else if (greatest_info.col % greatest_info.width == 0) { \ - fprintf(GREATEST_STDOUT, "\n"); \ + GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ greatest_info.col = 0; \ } \ - if (GREATEST_STDOUT == stdout) fflush(stdout); \ + fflush(GREATEST_STDOUT); \ } \ \ static void report_suite(void) { \ if (greatest_info.suite.tests_run > 0) { \ - fprintf(GREATEST_STDOUT, \ + GREATEST_FPRINTF(GREATEST_STDOUT, \ "\n%u test%s - %u passed, %u failed, %u skipped", \ greatest_info.suite.tests_run, \ greatest_info.suite.tests_run == 1 ? "" : "s", \ @@ -672,7 +752,7 @@ greatest_info.suite.skipped); \ GREATEST_CLOCK_DIFF(greatest_info.suite.pre_suite, \ greatest_info.suite.post_suite); \ - fprintf(GREATEST_STDOUT, "\n"); \ + GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ } \ } \ \ @@ -689,46 +769,63 @@ greatest_info.col = 0; \ } \ \ -static void greatest_run_suite(greatest_suite_cb *suite_cb, \ - const char *suite_name) { \ - if (greatest_info.suite_filter && \ - !greatest_name_match(suite_name, greatest_info.suite_filter)) { \ - return; \ +int greatest_suite_pre(const char *suite_name) { \ + struct greatest_prng *p = &greatest_info.prng[0]; \ + if (!greatest_name_match(suite_name, greatest_info.suite_filter, 1) \ + || (GREATEST_FIRST_FAIL() && greatest_info.failed > 0)) { \ + return 0; \ } \ + if (p->random_order) { \ + p->count++; \ + if (!p->initialized || ((p->count - 1) != p->state)) { \ + return 0; /* don't run this suite yet */ \ + } \ + } \ + p->count_run++; \ update_counts_and_reset_suite(); \ - if (GREATEST_FIRST_FAIL() && greatest_info.failed > 0) { return; } \ - fprintf(GREATEST_STDOUT, "\n* Suite %s:\n", suite_name); \ + GREATEST_FPRINTF(GREATEST_STDOUT, "\n* Suite %s:\n", suite_name); \ GREATEST_SET_TIME(greatest_info.suite.pre_suite); \ - suite_cb(); \ + return 1; \ +} \ + \ +void greatest_suite_post(void) { \ GREATEST_SET_TIME(greatest_info.suite.post_suite); \ report_suite(); \ } \ \ +static void greatest_run_suite(greatest_suite_cb *suite_cb, \ + const char *suite_name) { \ + if (greatest_suite_pre(suite_name)) { \ + suite_cb(); \ + greatest_suite_post(); \ + } \ +} \ + \ void greatest_do_pass(const char *name) { \ if (GREATEST_IS_VERBOSE()) { \ - fprintf(GREATEST_STDOUT, "PASS %s: %s", \ + GREATEST_FPRINTF(GREATEST_STDOUT, "PASS %s: %s", \ name, greatest_info.msg ? greatest_info.msg : ""); \ } else { \ - fprintf(GREATEST_STDOUT, "."); \ + GREATEST_FPRINTF(GREATEST_STDOUT, "."); \ } \ greatest_info.suite.passed++; \ } \ \ void greatest_do_fail(const char *name) { \ if (GREATEST_IS_VERBOSE()) { \ - fprintf(GREATEST_STDOUT, \ + GREATEST_FPRINTF(GREATEST_STDOUT, \ "FAIL %s: %s (%s:%u)", \ name, greatest_info.msg ? greatest_info.msg : "", \ greatest_info.fail_file, greatest_info.fail_line); \ } else { \ - fprintf(GREATEST_STDOUT, "F"); \ + GREATEST_FPRINTF(GREATEST_STDOUT, "F"); \ greatest_info.col++; \ /* add linebreak if in line of '.'s */ \ if (greatest_info.col != 0) { \ - fprintf(GREATEST_STDOUT, "\n"); \ + GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ greatest_info.col = 0; \ } \ - fprintf(GREATEST_STDOUT, "FAIL %s: %s (%s:%u)\n", \ + GREATEST_FPRINTF(GREATEST_STDOUT, "FAIL %s: %s (%s:%u)\n", \ name, \ greatest_info.msg ? greatest_info.msg : "", \ greatest_info.fail_file, greatest_info.fail_line); \ @@ -738,12 +835,12 @@ \ void greatest_do_skip(const char *name) { \ if (GREATEST_IS_VERBOSE()) { \ - fprintf(GREATEST_STDOUT, "SKIP %s: %s", \ + GREATEST_FPRINTF(GREATEST_STDOUT, "SKIP %s: %s", \ name, \ greatest_info.msg ? \ greatest_info.msg : "" ); \ } else { \ - fprintf(GREATEST_STDOUT, "s"); \ + GREATEST_FPRINTF(GREATEST_STDOUT, "s"); \ } \ greatest_info.suite.skipped++; \ } \ @@ -757,13 +854,13 @@ eq = type_info->equal(exp, got, udata); \ if (!eq) { \ if (type_info->print != NULL) { \ - fprintf(GREATEST_STDOUT, "\nExpected: "); \ + GREATEST_FPRINTF(GREATEST_STDOUT, "\nExpected: "); \ (void)type_info->print(exp, udata); \ - fprintf(GREATEST_STDOUT, "\n Got: "); \ + GREATEST_FPRINTF(GREATEST_STDOUT, "\n Got: "); \ (void)type_info->print(got, udata); \ - fprintf(GREATEST_STDOUT, "\n"); \ + GREATEST_FPRINTF(GREATEST_STDOUT, "\n"); \ } else { \ - fprintf(GREATEST_STDOUT, \ + GREATEST_FPRINTF(GREATEST_STDOUT, \ "GREATEST_ASSERT_EQUAL_T failure at %s:%u\n", \ greatest_info.fail_file, \ greatest_info.fail_line); \ @@ -773,63 +870,73 @@ } \ \ void greatest_usage(const char *name) { \ - fprintf(GREATEST_STDOUT, \ - "Usage: %s [-hlfv] [-s SUITE] [-t TEST]\n" \ + GREATEST_FPRINTF(GREATEST_STDOUT, \ + "Usage: %s [--help] [-hlfv] [-s SUITE] [-t TEST]\n" \ " -h, --help print this Help\n" \ - " -l List suites and their tests, then exit\n" \ + " -l List suites and tests, then exit (dry run)\n" \ " -f Stop runner after first failure\n" \ " -v Verbose output\n" \ " -s SUITE only run suites containing string SUITE\n" \ - " -t TEST only run tests containing string TEST\n", \ + " -t TEST only run tests containing string TEST\n" \ + " -x EXCLUDE exclude tests containing string EXCLUDE\n", \ name); \ } \ \ -static void greatest_parse_args(int argc, char **argv) { \ +static void greatest_parse_options(int argc, char **argv) { \ int i = 0; \ for (i = 1; i < argc; i++) { \ - if (0 == strncmp("-t", argv[i], 2)) { \ - if (argc <= i + 1) { \ - greatest_usage(argv[0]); \ - exit(EXIT_FAILURE); \ + if (argv[i][0] == '-') { \ + char f = argv[i][1]; \ + if ((f == 's' || f == 't' || f == 'x') && argc <= i + 1) { \ + greatest_usage(argv[0]); exit(EXIT_FAILURE); \ } \ - greatest_info.test_filter = argv[i+1]; \ - i++; \ - } else if (0 == strncmp("-s", argv[i], 2)) { \ - if (argc <= i + 1) { \ + switch (f) { \ + case 's': /* suite name filter */ \ + greatest_set_suite_filter(argv[i + 1]); i++; break; \ + case 't': /* test name filter */ \ + greatest_set_test_filter(argv[i + 1]); i++; break; \ + case 'x': /* test name exclusion */ \ + greatest_set_test_exclude(argv[i + 1]); i++; break; \ + case 'f': /* first fail flag */ \ + greatest_stop_at_first_fail(); break; \ + case 'l': /* list only (dry run) */ \ + greatest_info.flags |= GREATEST_FLAG_LIST_ONLY; break; \ + case 'v': /* first fail flag */ \ + greatest_info.verbosity++; break; \ + case 'h': /* help */ \ + greatest_usage(argv[0]); exit(EXIT_SUCCESS); \ + case '-': \ + if (0 == strncmp("--help", argv[i], 6)) { \ + greatest_usage(argv[0]); exit(EXIT_SUCCESS); \ + } else if (0 == strncmp("--", argv[i], 2)) { \ + return; /* ignore following arguments */ \ + } /* fall through */ \ + default: \ + GREATEST_FPRINTF(GREATEST_STDOUT, \ + "Unknown argument '%s'\n", argv[i]); \ greatest_usage(argv[0]); \ exit(EXIT_FAILURE); \ } \ - greatest_info.suite_filter = argv[i+1]; \ - i++; \ - } else if (0 == strncmp("-f", argv[i], 2)) { \ - greatest_info.flags |= GREATEST_FLAG_FIRST_FAIL; \ - } else if (0 == strncmp("-v", argv[i], 2)) { \ - greatest_info.verbosity++; \ - } else if (0 == strncmp("-l", argv[i], 2)) { \ - greatest_info.flags |= GREATEST_FLAG_LIST_ONLY; \ - } else if (0 == strncmp("-h", argv[i], 2) || \ - 0 == strncmp("--help", argv[i], 6)) { \ - greatest_usage(argv[0]); \ - exit(EXIT_SUCCESS); \ - } else if (0 == strncmp("--", argv[i], 2)) { \ - break; \ - } else { \ - fprintf(GREATEST_STDOUT, \ - "Unknown argument '%s'\n", argv[i]); \ - greatest_usage(argv[0]); \ - exit(EXIT_FAILURE); \ } \ } \ } \ \ int greatest_all_passed(void) { return (greatest_info.failed == 0); } \ \ -void greatest_set_test_filter(const char *name) { \ - greatest_info.test_filter = name; \ +void greatest_set_test_filter(const char *filter) { \ + greatest_info.test_filter = filter; \ +} \ + \ +void greatest_set_test_exclude(const char *filter) { \ + greatest_info.test_exclude = filter; \ +} \ + \ +void greatest_set_suite_filter(const char *filter) { \ + greatest_info.suite_filter = filter; \ } \ \ -void greatest_set_suite_filter(const char *name) { \ - greatest_info.suite_filter = name; \ +void greatest_stop_at_first_fail(void) { \ + greatest_info.flags |= GREATEST_FLAG_FIRST_FAIL; \ } \ \ void greatest_get_report(struct greatest_report_t *report) { \ @@ -874,7 +981,7 @@ \ static int greatest_string_printf_cb(const void *t, void *udata) { \ (void)udata; /* note: does not check \0 termination. */ \ - return fprintf(GREATEST_STDOUT, "%s", (const char *)t); \ + return GREATEST_FPRINTF(GREATEST_STDOUT, "%s", (const char *)t); \ } \ \ greatest_type_info greatest_type_info_string = { \ @@ -888,9 +995,11 @@ return (0 == memcmp(exp, got, env->size)); \ } \ \ +/* Hexdump raw memory, with differences highlighted */ \ static int greatest_memory_printf_cb(const void *t, void *udata) { \ greatest_memory_cmp_env *env = (greatest_memory_cmp_env *)udata; \ - unsigned char *buf = (unsigned char *)t, diff_mark = ' '; \ + const unsigned char *buf = (const unsigned char *)t; \ + unsigned char diff_mark = ' '; \ FILE *out = GREATEST_STDOUT; \ size_t i, line_i, line_len = 0; \ int len = 0; /* format hexdump with differences highlighted */ \ @@ -901,24 +1010,64 @@ for (line_i = i; line_i < i + line_len; line_i++) { \ if (env->exp[line_i] != env->got[line_i]) diff_mark = 'X'; \ } \ - len += fprintf(out, "\n%04x %c ", (unsigned int)i, diff_mark); \ + len += GREATEST_FPRINTF(out, "\n%04x %c ", \ + (unsigned int)i, diff_mark); \ for (line_i = i; line_i < i + line_len; line_i++) { \ int m = env->exp[line_i] == env->got[line_i]; /* match? */ \ - len += fprintf(out, "%02x%c", buf[line_i], m ? ' ' : '<'); \ + len += GREATEST_FPRINTF(out, "%02x%c", \ + buf[line_i], m ? ' ' : '<'); \ } \ for (line_i = 0; line_i < 16 - line_len; line_i++) { \ - len += fprintf(out, " "); \ + len += GREATEST_FPRINTF(out, " "); \ } \ - fprintf(out, " "); \ + GREATEST_FPRINTF(out, " "); \ for (line_i = i; line_i < i + line_len; line_i++) { \ unsigned char c = buf[line_i]; \ - len += fprintf(out, "%c", isprint(c) ? c : '.'); \ + len += GREATEST_FPRINTF(out, "%c", isprint(c) ? c : '.'); \ } \ } \ - len += fprintf(out, "\n"); \ + len += GREATEST_FPRINTF(out, "\n"); \ return len; \ } \ \ +void greatest_prng_init_first_pass(int id) { \ + greatest_info.prng[id].random_order = 1; \ + greatest_info.prng[id].count_run = 0; \ +} \ + \ +int greatest_prng_init_second_pass(int id, unsigned long seed) { \ + static unsigned long primes[] = { 11, 101, 1009, 10007, \ + 100003, 1000003, 10000019, 100000007, 1000000007, \ + 1538461, 1865471, 17471, 2147483647 /* 2**32 - 1 */, }; \ + struct greatest_prng *prng = &greatest_info.prng[id]; \ + if (prng->count == 0) { return 0; } \ + prng->mod = 1; \ + prng->count_ceil = prng->count; \ + while (prng->mod < prng->count) { prng->mod <<= 1; } \ + prng->state = seed & 0x1fffffff; /* only use lower 29 bits... */ \ + prng->a = (4LU * prng->state) + 1; /* to avoid overflow */ \ + prng->c = primes[(seed * 16451) % sizeof(primes)/sizeof(primes[0])];\ + prng->initialized = 1; \ + return 1; \ +} \ + \ +/* Step the pseudorandom number generator until its state reaches \ + * another test ID between 0 and the test count. \ + * This use a linear congruential pseudorandom number generator, \ + * with the power-of-two ceiling of the test count as the modulus, the \ + * masked seed as the multiplier, and a prime as the increment. For \ + * each generated value < the test count, run the corresponding test. \ + * This will visit all IDs 0 <= X < mod once before repeating, \ + * with a starting position chosen based on the initial seed. \ + * For details, see: Knuth, The Art of Computer Programming \ + * Volume. 2, section 3.2.1. */ \ +void greatest_prng_step(int id) { \ + struct greatest_prng *p = &greatest_info.prng[id]; \ + do { \ + p->state = ((p->a * p->state) + p->c) & (p->mod - 1); \ + } while (p->state >= p->count_ceil); \ +} \ + \ greatest_type_info greatest_type_info_memory = { \ greatest_memory_equal_cb, \ greatest_memory_printf_cb, \ @@ -931,7 +1080,10 @@ do { \ /* Suppress unused function warning if features aren't used */ \ (void)greatest_run_suite; \ - (void)greatest_parse_args; \ + (void)greatest_parse_options; \ + (void)greatest_prng_step; \ + (void)greatest_prng_init_first_pass; \ + (void)greatest_prng_init_second_pass; \ \ memset(&greatest_info, 0, sizeof(greatest_info)); \ greatest_info.width = GREATEST_DEFAULT_WIDTH; \ @@ -942,7 +1094,7 @@ #define GREATEST_MAIN_BEGIN() \ do { \ GREATEST_INIT(); \ - greatest_parse_args(argc, argv); \ + greatest_parse_options(argc, argv); \ } while (0) /* Report passes, failures, skipped tests, the number of @@ -952,16 +1104,16 @@ if (!GREATEST_LIST_ONLY()) { \ update_counts_and_reset_suite(); \ GREATEST_SET_TIME(greatest_info.end); \ - fprintf(GREATEST_STDOUT, \ + GREATEST_FPRINTF(GREATEST_STDOUT, \ "\nTotal: %u test%s", \ greatest_info.tests_run, \ greatest_info.tests_run == 1 ? "" : "s"); \ GREATEST_CLOCK_DIFF(greatest_info.begin, \ greatest_info.end); \ - fprintf(GREATEST_STDOUT, ", %u assertion%s\n", \ + GREATEST_FPRINTF(GREATEST_STDOUT, ", %u assertion%s\n", \ greatest_info.assertions, \ greatest_info.assertions == 1 ? "" : "s"); \ - fprintf(GREATEST_STDOUT, \ + GREATEST_FPRINTF(GREATEST_STDOUT, \ "Pass: %u, fail: %u, skip: %u.\n", \ greatest_info.passed, \ greatest_info.failed, greatest_info.skipped); \ @@ -1014,6 +1166,8 @@ #define SET_SETUP GREATEST_SET_SETUP_CB #define SET_TEARDOWN GREATEST_SET_TEARDOWN_CB #define CHECK_CALL GREATEST_CHECK_CALL +#define SHUFFLE_TESTS GREATEST_SHUFFLE_TESTS +#define SHUFFLE_SUITES GREATEST_SHUFFLE_SUITES #ifdef GREATEST_VA_ARGS #define RUN_TESTp GREATEST_RUN_TESTp @@ -1028,7 +1182,7 @@ #endif /* USE_ABBREVS */ -#ifdef __cplusplus +#if defined(__cplusplus) && !defined(GREATEST_NO_EXTERN_CPLUSPLUS) } #endif diff -Nru fzy-0.9/fzy.1 fzy-1.0/fzy.1 --- fzy-0.9/fzy.1 2017-04-17 07:55:42.000000000 +0000 +++ fzy-1.0/fzy.1 2018-09-23 22:05:55.000000000 +0000 @@ -1,4 +1,4 @@ -.TH FZY 1 "2017-04-17" "fzy 0.9" +.TH FZY 1 "2018-09-23" "fzy 1.0" .SH NAME fzy \- A fuzzy text selector menu for the terminal. .SH SYNOPSIS @@ -54,6 +54,9 @@ .BR "ENTER" Print the selected item to stdout and exit .TP +.BR "Ctrl+c, Esc" +Exit with status 1, without making a selection. +.TP .BR "Up Arrow, Ctrl+p" Select the previous item .TP diff -Nru fzy-0.9/Makefile fzy-1.0/Makefile --- fzy-0.9/Makefile 2017-04-17 07:55:42.000000000 +0000 +++ fzy-1.0/Makefile 2018-09-23 22:05:55.000000000 +0000 @@ -1,4 +1,4 @@ -VERSION=0.9 +VERSION=1.0 CPPFLAGS=-DVERSION=\"${VERSION}\" -D_GNU_SOURCE CFLAGS+=-Wall -Wextra -g -std=c99 -O3 -pedantic -Ideps @@ -22,7 +22,7 @@ $(CC) $(CFLAGS) $(CCFLAGS) -Isrc -o $@ $(TESTOBJECTS) $(LIBS) acceptance: fzy - cd test/acceptance && bundle && bundle exec ruby acceptance_test.rb + cd test/acceptance && bundle --quiet && bundle exec ruby acceptance_test.rb test: check check: test/fzytest diff -Nru fzy-0.9/README.md fzy-1.0/README.md --- fzy-0.9/README.md 2017-04-17 07:55:42.000000000 +0000 +++ fzy-1.0/README.md 2018-09-23 22:05:55.000000000 +0000 @@ -3,7 +3,7 @@ **fzy** is a fast, simple fuzzy text selector for the terminal with an advanced [scoring algorithm](#sorting). -![](http://i.hawth.ca/u/fzy2.gif) +![](http://i.hawth.ca/u/fzy_animated_demo.svg)
It's been kind of life-changing. @@ -33,33 +33,35 @@ ## Installation -### From source +**macOS** - make - sudo make install +Using Homebrew -The `PREFIX` environment variable can be used to specify the install location, -the default is `/usr/local`. + brew install fzy -### Ubuntu/Debian 64-bit +Using MacPorts - wget https://github.com/jhawthorn/fzy/releases/download/0.9/fzy_0.9-1_amd64.deb - sudo dpkg -i fzy_0.9-1_amd64.deb + sudo port install fzy -### Fedora/Redhat/CentOS +**[Arch Linux](https://www.archlinux.org/packages/?sort=&q=fzy&maintainer=&flagged=)/MSYS2**: `pacman -S fzy` - sudo yum install https://github.com/jhawthorn/fzy/releases/download/0.9/fzy-0.9-1.x86_64.rpm +**[FreeBSD](https://www.freebsd.org/cgi/ports.cgi?query=fzy&stype=all)**: `pkg install fzy` -### Arch Linux +**[Gentoo Linux](https://packages.gentoo.org/packages/app-shells/fzy)**: `emerge -av app-shells/fzy` -fzy is available -[in the AUR](https://aur.archlinux.org/packages/fzy/) -with a very simple -[PKGBUILD](https://aur.archlinux.org/cgit/aur.git/tree/PKGBUILD?h=fzy) +**[Ubuntu](https://packages.ubuntu.com/search?keywords=fzy&searchon=names&suite=bionic§ion=all)/[Debian](https://packages.debian.org/search?keywords=fzy&searchon=names&suite=all§ion=all)**: `apt-get install fzy` -### OS X via Homebrew +**[pkgsrc](http://pkgsrc.se/misc/fzy) (NetBSD and others)**: `pkgin install fzy` - brew install fzy +**[openSUSE](https://software.opensuse.org/package/fzy)**: `zypper in fzy` + +### From source + + make + sudo make install + +The `PREFIX` environment variable can be used to specify the install location, +the default is `/usr/local`. ## Usage @@ -90,9 +92,9 @@ Any program can be used to filter files presented through fzy. [ag (the silver searcher)](https://github.com/ggreer/the_silver_searcher) can be used to ignore files specified by `.gitignore`. ``` vim -nnoremap e :call FzyCommand("ag . -l -g ''", ":e") -nnoremap v :call FzyCommand("ag . -l -g ''", ":vs") -nnoremap s :call FzyCommand("ag . -l -g ''", ":sp") +nnoremap e :call FzyCommand("ag . --silent -l -g ''", ":e") +nnoremap v :call FzyCommand("ag . --silent -l -g ''", ":vs") +nnoremap s :call FzyCommand("ag . --silent -l -g ''", ":sp") ``` ## Sorting diff -Nru fzy-0.9/src/choices.c fzy-1.0/src/choices.c --- fzy-0.9/src/choices.c 2017-04-17 07:55:42.000000000 +0000 +++ fzy-1.0/src/choices.c 2018-09-23 22:05:55.000000000 +0000 @@ -21,7 +21,7 @@ if (a->score == b->score) { /* To ensure a stable sort, we must also sort by the string - * pointers. We can do this since we know all the stings are + * pointers. We can do this since we know all the strings are * from a contiguous memory segment (buffer in choices_t). */ if (a->str < b->str) { diff -Nru fzy-0.9/src/config.def.h fzy-1.0/src/config.def.h --- fzy-0.9/src/config.def.h 2017-04-17 07:55:42.000000000 +0000 +++ fzy-1.0/src/config.def.h 2018-09-23 22:05:55.000000000 +0000 @@ -8,3 +8,6 @@ #define SCORE_MATCH_WORD 0.8 #define SCORE_MATCH_CAPITAL 0.7 #define SCORE_MATCH_DOT 0.6 + +/* Time (in ms) to wait for additional bytes of an escape sequence */ +#define KEYTIMEOUT 25 diff -Nru fzy-0.9/src/fzy.c fzy-1.0/src/fzy.c --- fzy-0.9/src/fzy.c 2017-04-17 07:55:42.000000000 +0000 +++ fzy-1.0/src/fzy.c 2018-09-23 22:05:55.000000000 +0000 @@ -3,6 +3,7 @@ #include #include #include +#include #include "match.h" #include "tty.h" @@ -20,16 +21,17 @@ choices_t choices; choices_init(&choices, &options); - choices_fread(&choices, stdin); if (options.benchmark) { if (!options.filter) { fprintf(stderr, "Must specify -e/--show-matches with --benchmark\n"); exit(EXIT_FAILURE); } + choices_fread(&choices, stdin); for (int i = 0; i < options.benchmark; i++) choices_search(&choices, options.filter); } else if (options.filter) { + choices_fread(&choices, stdin); choices_search(&choices, options.filter); for (size_t i = 0; i < choices_available(&choices); i++) { if (options.show_scores) @@ -38,9 +40,16 @@ } } else { /* interactive */ + + if (isatty(STDIN_FILENO)) + choices_fread(&choices, stdin); + tty_t tty; tty_init(&tty, options.tty_filename); + if (!isatty(STDIN_FILENO)) + choices_fread(&choices, stdin); + if (options.num_lines > choices.size) options.num_lines = choices.size; diff -Nru fzy-0.9/src/options.c fzy-1.0/src/options.c --- fzy-0.9/src/options.c 2017-04-17 07:55:42.000000000 +0000 +++ fzy-1.0/src/options.c 2018-09-23 22:05:55.000000000 +0000 @@ -55,7 +55,7 @@ while ((c = getopt_long(argc, argv, "vhse:q:l:t:p:j:", longopts, NULL)) != -1) { switch (c) { case 'v': - printf("%s " VERSION " (c) 2014 John Hawthorn\n", argv[0]); + printf("%s " VERSION " © 2014-2018 John Hawthorn\n", argv[0]); exit(EXIT_SUCCESS); case 's': options->show_scores = 1; diff -Nru fzy-0.9/src/tty.c fzy-1.0/src/tty.c --- fzy-0.9/src/tty.c 2017-04-17 07:55:42.000000000 +0000 +++ fzy-1.0/src/tty.c 2018-09-23 22:05:55.000000000 +0000 @@ -6,9 +6,13 @@ #include #include #include +#include +#include #include "tty.h" +#include "../config.h" + void tty_reset(tty_t *tty) { tcsetattr(tty->fdin, TCSANOW, &tty->original_termios); } @@ -19,6 +23,10 @@ close(tty->fdin); } +static void handle_sigwinch(int sig){ + (void)sig; +} + void tty_init(tty_t *tty, const char *tty_filename) { tty->fdin = open(tty_filename, O_RDONLY); if (tty->fdin < 0) { @@ -60,6 +68,8 @@ tty_getwinsz(tty); tty_setnormal(tty); + + signal(SIGWINCH, handle_sigwinch); } void tty_getwinsz(tty_t *tty) { @@ -87,12 +97,36 @@ } } -int tty_input_ready(tty_t *tty) { +int tty_input_ready(tty_t *tty, long int timeout, int return_on_signal) { fd_set readfs; - struct timeval tv = {0, 0}; + FD_ZERO(&readfs); FD_SET(tty->fdin, &readfs); - select(tty->fdin + 1, &readfs, NULL, NULL, &tv); - return FD_ISSET(tty->fdin, &readfs); + + struct timespec ts = {timeout / 1000, (timeout % 1000) * 1000000}; + + sigset_t mask; + sigemptyset(&mask); + if (!return_on_signal) + sigaddset(&mask, SIGWINCH); + + int err = pselect( + tty->fdin + 1, + &readfs, + NULL, + NULL, + timeout < 0 ? NULL : &ts, + return_on_signal ? NULL : &mask); + + if (err < 0) { + if (errno == EINTR) { + return 0; + } else { + perror("select"); + exit(EXIT_FAILURE); + } + } else { + return FD_ISSET(tty->fdin, &readfs); + } } static void tty_sgr(tty_t *tty, int code) { @@ -110,11 +144,23 @@ tty_sgr(tty, 7); } +void tty_setunderline(tty_t *tty) { + tty_sgr(tty, 4); +} + void tty_setnormal(tty_t *tty) { tty_sgr(tty, 0); tty->fgcolor = 9; } +void tty_setnowrap(tty_t *tty) { + tty_printf(tty, "%c%c?7l", 0x1b, '['); +} + +void tty_setwrap(tty_t *tty) { + tty_printf(tty, "%c%c?7h", 0x1b, '['); +} + void tty_newline(tty_t *tty) { tty_printf(tty, "%c%cK\n", 0x1b, '['); } diff -Nru fzy-0.9/src/tty.h fzy-1.0/src/tty.h --- fzy-0.9/src/tty.h 2017-04-17 07:55:42.000000000 +0000 +++ fzy-1.0/src/tty.h 2018-09-23 22:05:55.000000000 +0000 @@ -17,11 +17,14 @@ void tty_init(tty_t *tty, const char *tty_filename); void tty_getwinsz(tty_t *tty); char tty_getchar(tty_t *tty); -int tty_input_ready(tty_t *tty); +int tty_input_ready(tty_t *tty, long int timeout, int return_on_signal); void tty_setfg(tty_t *tty, int fg); void tty_setinvert(tty_t *tty); +void tty_setunderline(tty_t *tty); void tty_setnormal(tty_t *tty); +void tty_setnowrap(tty_t *tty); +void tty_setwrap(tty_t *tty); #define TTY_COLOR_BLACK 0 #define TTY_COLOR_RED 1 diff -Nru fzy-0.9/src/tty_interface.c fzy-1.0/src/tty_interface.c --- fzy-0.9/src/tty_interface.c 2017-04-17 07:55:42.000000000 +0000 +++ fzy-1.0/src/tty_interface.c 2018-09-23 22:05:55.000000000 +0000 @@ -7,6 +7,14 @@ #include "tty_interface.h" #include "../config.h" +static int isprint_unicode(char c) { + return isprint(c) || c & (1 << 7); +} + +static int is_boundary(char c) { + return ~c & (1 << 7) || c & (1 << 6); +} + static void clear(tty_interface_t *state) { tty_t *tty = state->tty; @@ -34,8 +42,6 @@ score_t score = match_positions(search, choice, &positions[0]); - size_t maxwidth = tty_getwidth(tty); - if (options->show_scores) { if (score == SCORE_MIN) { tty_printf(tty, "( ) "); @@ -45,22 +51,23 @@ } if (selected) +#ifdef TTY_SELECTION_UNDERLINE + tty_setunderline(tty); +#else tty_setinvert(tty); +#endif + tty_setnowrap(tty); for (size_t i = 0, p = 0; choice[i] != '\0'; i++) { - if (i + 1 < maxwidth) { - if (positions[p] == i) { - tty_setfg(tty, TTY_COLOR_HIGHLIGHT); - p++; - } else { - tty_setfg(tty, TTY_COLOR_NORMAL); - } - tty_printf(tty, "%c", choice[i]); + if (positions[p] == i) { + tty_setfg(tty, TTY_COLOR_HIGHLIGHT); + p++; } else { - tty_printf(tty, "$"); - break; + tty_setfg(tty, TTY_COLOR_NORMAL); } + tty_printf(tty, "%c", choice[i]); } + tty_setwrap(tty); tty_setnormal(tty); } @@ -93,7 +100,11 @@ if (num_lines > 0) { tty_moveup(tty, num_lines); } - tty_setcol(tty, strlen(options->prompt) + strlen(state->search)); + + tty_setcol(tty, 0); + fputs(options->prompt, tty->fout); + for (size_t i = 0; i < state->cursor; i++) + fputc(state->search[i], tty->fout); tty_flush(tty); } @@ -131,20 +142,36 @@ } static void action_del_char(tty_interface_t *state) { - if (*state->search) - state->search[strlen(state->search) - 1] = '\0'; + size_t length = strlen(state->search); + if (state->cursor == 0) { + return; + } + size_t original_cursor = state->cursor; + + do { + state->cursor--; + } while (!is_boundary(state->search[state->cursor]) && state->cursor); + + memmove(&state->search[state->cursor], &state->search[original_cursor], length - original_cursor + 1); } static void action_del_word(tty_interface_t *state) { - size_t search_size = strlen(state->search); - if (search_size) - state->search[--search_size] = '\0'; - while (search_size && !isspace(state->search[--search_size])) - state->search[search_size] = '\0'; + size_t original_cursor = state->cursor; + size_t cursor = state->cursor; + + while (cursor && isspace(state->search[cursor - 1])) + cursor--; + + while (cursor && !isspace(state->search[cursor - 1])) + cursor--; + + memmove(&state->search[cursor], &state->search[original_cursor], strlen(state->search) - original_cursor + 1); + state->cursor = cursor; } static void action_del_all(tty_interface_t *state) { - strcpy(state->search, ""); + memmove(state->search, &state->search[state->cursor], strlen(state->search) - state->cursor + 1); + state->cursor = 0; } static void action_prev(tty_interface_t *state) { @@ -152,20 +179,48 @@ choices_prev(state->choices); } +static void action_ignore(tty_interface_t *state) { + (void)state; +} + static void action_next(tty_interface_t *state) { update_state(state); choices_next(state->choices); } +static void action_left(tty_interface_t *state) { + if (state->cursor > 0) { + state->cursor--; + while (!is_boundary(state->search[state->cursor]) && state->cursor) + state->cursor--; + } +} + +static void action_right(tty_interface_t *state) { + if (state->cursor < strlen(state->search)) { + state->cursor++; + while (!is_boundary(state->search[state->cursor])) + state->cursor++; + } +} + +static void action_beginning(tty_interface_t *state) { + state->cursor = 0; +} + +static void action_end(tty_interface_t *state) { + state->cursor = strlen(state->search); +} + static void action_pageup(tty_interface_t *state) { update_state(state); - for(size_t i = 0; i < state->options->num_lines && state->choices->selection > 0; i++) + for (size_t i = 0; i < state->options->num_lines && state->choices->selection > 0; i++) choices_prev(state->choices); } static void action_pagedown(tty_interface_t *state) { update_state(state); - for(size_t i = 0; i < state->options->num_lines && state->choices->selection < state->choices->available-1; i++) + for (size_t i = 0; i < state->options->num_lines && state->choices->selection < state->choices->available - 1; i++) choices_next(state->choices); } @@ -174,6 +229,7 @@ const char *current_selection = choices_get(state->choices, state->choices->selection); if (current_selection) { strncpy(state->search, choices_get(state->choices, state->choices->selection), SEARCH_SIZE_MAX); + state->cursor = strlen(state->search); } } @@ -188,8 +244,10 @@ char *search = state->search; size_t search_size = strlen(search); if (search_size < SEARCH_SIZE_MAX) { - search[search_size++] = ch; - search[search_size] = '\0'; + memmove(&search[state->cursor+1], &search[state->cursor], search_size - state->cursor + 1); + search[state->cursor] = ch; + + state->cursor++; } } @@ -197,6 +255,7 @@ state->tty = tty; state->choices = choices; state->options = options; + state->ambiguous_key_pending = 0; strcpy(state->input, ""); strcpy(state->search, ""); @@ -207,6 +266,8 @@ if (options->init_search) strncpy(state->search, options->init_search, SEARCH_SIZE_MAX); + state->cursor = strlen(state->search); + update_search(state); } @@ -217,7 +278,9 @@ #define KEY_CTRL(key) ((const char[]){((key) - ('@')), '\0'}) -static const keybinding_t keybindings[] = {{"\x7f", action_del_char}, /* DEL */ +static const keybinding_t keybindings[] = {{"\x1b", action_exit}, /* ESC */ + {"\x7f", action_del_char}, /* DEL */ + {KEY_CTRL('H'), action_del_char}, /* Backspace (C-H) */ {KEY_CTRL('W'), action_del_word}, /* C-W */ {KEY_CTRL('U'), action_del_all}, /* C-U */ @@ -227,40 +290,69 @@ {KEY_CTRL('M'), action_emit}, /* CR */ {KEY_CTRL('P'), action_prev}, /* C-P */ {KEY_CTRL('N'), action_next}, /* C-N */ - {KEY_CTRL('K'), action_prev}, /* C-J */ - {KEY_CTRL('J'), action_next}, /* C-K */ - + {KEY_CTRL('K'), action_prev}, /* C-K */ + {KEY_CTRL('J'), action_next}, /* C-J */ + {KEY_CTRL('A'), action_beginning}, /* C-A */ + {KEY_CTRL('E'), action_end}, /* C-E */ + + {"\x1bOD", action_left}, /* LEFT */ + {"\x1b[D", action_left}, /* LEFT */ + {"\x1bOC", action_right}, /* RIGHT */ + {"\x1b[C", action_right}, /* RIGHT */ + {"\x1b[1~", action_beginning}, /* HOME */ + {"\x1b[H", action_beginning}, /* HOME */ + {"\x1b[4~", action_end}, /* END */ + {"\x1b[F", action_end}, /* END */ {"\x1b[A", action_prev}, /* UP */ {"\x1bOA", action_prev}, /* UP */ {"\x1b[B", action_next}, /* DOWN */ {"\x1bOB", action_next}, /* DOWN */ {"\x1b[5~", action_pageup}, {"\x1b[6~", action_pagedown}, + {"\x1b[200~", action_ignore}, + {"\x1b[201~", action_ignore}, {NULL, NULL}}; #undef KEY_CTRL -static void handle_input(tty_interface_t *state, const char *s) { +static void handle_input(tty_interface_t *state, const char *s, int handle_ambiguous_key) { + state->ambiguous_key_pending = 0; + char *input = state->input; strcat(state->input, s); - /* See if we have matched a keybinding */ + /* Figure out if we have completed a keybinding and whether we're in the + * middle of one (both can happen, because of Esc). */ + int found_keybinding = -1; + int in_middle = 0; for (int i = 0; keybindings[i].key; i++) { - if (!strcmp(input, keybindings[i].key)) { - keybindings[i].action(state); - strcpy(input, ""); - return; - } + if (!strcmp(input, keybindings[i].key)) + found_keybinding = i; + else if (!strncmp(input, keybindings[i].key, strlen(state->input))) + in_middle = 1; } - /* Check if we are in the middle of a keybinding */ - for (int i = 0; keybindings[i].key; i++) - if (!strncmp(input, keybindings[i].key, strlen(input))) - return; + /* If we have an unambiguous keybinding, run it. */ + if (found_keybinding != -1 && (!in_middle || handle_ambiguous_key)) { + keybindings[found_keybinding].action(state); + strcpy(input, ""); + return; + } + + /* We could have a complete keybinding, or could be in the middle of one. + * We'll need to wait a few milliseconds to find out. */ + if (found_keybinding != -1 && in_middle) { + state->ambiguous_key_pending = 1; + return; + } + + /* Wait for more if we are in the middle of a keybinding */ + if (in_middle) + return; /* No matching keybinding, add to search */ for (int i = 0; input[i]; i++) - if (isprint(input[i])) + if (isprint_unicode(input[i])) append_search(state, input[i]); /* We have processed the input, so clear it */ @@ -272,14 +364,27 @@ for (;;) { do { + while(!tty_input_ready(state->tty, -1, 1)) { + /* We received a signal (probably WINCH) */ + draw(state); + } + char s[2] = {tty_getchar(state->tty), '\0'}; - handle_input(state, s); + handle_input(state, s, 0); if (state->exit >= 0) return state->exit; draw(state); - } while (tty_input_ready(state->tty)); + } while (tty_input_ready(state->tty, state->ambiguous_key_pending ? KEYTIMEOUT : 0, 0)); + + if (state->ambiguous_key_pending) { + char s[1] = ""; + handle_input(state, s, 1); + + if (state->exit >= 0) + return state->exit; + } update_state(state); } diff -Nru fzy-0.9/src/tty_interface.h fzy-1.0/src/tty_interface.h --- fzy-0.9/src/tty_interface.h 2017-04-17 07:55:42.000000000 +0000 +++ fzy-1.0/src/tty_interface.h 2018-09-23 22:05:55.000000000 +0000 @@ -14,7 +14,9 @@ char search[SEARCH_SIZE_MAX + 1]; char last_search[SEARCH_SIZE_MAX + 1]; + size_t cursor; + int ambiguous_key_pending; char input[32]; /* Pending input buffer */ int exit; diff -Nru fzy-0.9/test/acceptance/acceptance_test.rb fzy-1.0/test/acceptance/acceptance_test.rb --- fzy-0.9/test/acceptance/acceptance_test.rb 2017-04-17 07:55:42.000000000 +0000 +++ fzy-1.0/test/acceptance/acceptance_test.rb 2018-09-23 22:05:55.000000000 +0000 @@ -5,14 +5,11 @@ class FzyTest < Minitest::Test FZY_PATH = File.expand_path('../../../fzy', __FILE__) - def setup - # fzy is fast. - # This is never hit in a (passing) test suite, but helps speed up development - TTYtest.default_max_wait_time = 0.2 - end + LEFT = "\e[D" + RIGHT = "\e[C" def test_empty_list - @tty = TTYtest.new_terminal(%{echo placeholder;echo -n "" | #{FZY_PATH}}) + @tty = interactive_fzy(input: %w[], before: "placeholder") @tty.assert_cursor_position(y: 1, x: 2) @tty.assert_matches <<~TTY placeholder @@ -42,7 +39,7 @@ end def test_one_item - @tty = TTYtest.new_terminal(%{echo placeholder;echo -n "test" | #{FZY_PATH}}) + @tty = interactive_fzy(input: %w[test], before: "placeholder") @tty.assert_matches <<~TTY placeholder > @@ -74,7 +71,7 @@ end def test_two_items - @tty = TTYtest.new_terminal(%{echo placeholder;echo -n "test\nfoo" | #{FZY_PATH}}) + @tty = interactive_fzy(input: %w[test foo], before: "placeholder") @tty.assert_cursor_position(y: 1, x: 2) @tty.assert_matches <<~TTY placeholder @@ -111,7 +108,7 @@ end def test_editing - @tty = TTYtest.new_terminal(%{echo placeholder;echo -n "test\nfoo" | #{FZY_PATH}}) + @tty = interactive_fzy(input: %w[test foo], before: "placeholder") @tty.assert_cursor_position(y: 1, x: 2) @tty.assert_matches <<~TTY placeholder @@ -152,7 +149,7 @@ end def test_ctrl_d - @tty = TTYtest.new_terminal(%{echo -n "foo\nbar" | #{FZY_PATH}}) + @tty = interactive_fzy(input: %w[foo bar]) @tty.assert_matches ">\nfoo\nbar" @tty.send_keys('foo') @@ -164,7 +161,7 @@ end def test_ctrl_c - @tty = TTYtest.new_terminal(%{echo -n "foo\nbar" | #{FZY_PATH}}) + @tty = interactive_fzy(input: %w[foo bar]) @tty.assert_matches ">\nfoo\nbar" @tty.send_keys('foo') @@ -176,25 +173,25 @@ end def test_down_arrow - @tty = TTYtest.new_terminal(%{echo -n "foo\nbar" | #{FZY_PATH}}) + @tty = interactive_fzy(input: %w[foo bar]) @tty.assert_matches ">\nfoo\nbar" @tty.send_keys("\e[A\r") @tty.assert_matches "bar" - @tty = TTYtest.new_terminal(%{echo -n "foo\nbar" | #{FZY_PATH}}) + @tty = interactive_fzy(input: %w[foo bar]) @tty.assert_matches ">\nfoo\nbar" @tty.send_keys("\eOA\r") @tty.assert_matches "bar" end def test_up_arrow - @tty = TTYtest.new_terminal(%{echo -n "foo\nbar" | #{FZY_PATH}}) + @tty = interactive_fzy(input: %w[foo bar]) @tty.assert_matches ">\nfoo\nbar" @tty.send_keys("\e[A") # first down @tty.send_keys("\e[B\r") # and back up @tty.assert_matches "foo" - @tty = TTYtest.new_terminal(%{echo -n "foo\nbar" | #{FZY_PATH}}) + @tty = interactive_fzy(input: %w[foo bar]) @tty.assert_matches ">\nfoo\nbar" @tty.send_keys("\eOA") # first down @tty.send_keys("\e[B\r") # and back up @@ -202,45 +199,48 @@ end def test_lines - @tty = TTYtest.new_terminal(%{seq 10 | #{FZY_PATH}}) + input10 = (1..10).map(&:to_s) + input20 = (1..20).map(&:to_s) + + @tty = interactive_fzy(input: input10) @tty.assert_matches ">\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10" - @tty = TTYtest.new_terminal(%{seq 20 | #{FZY_PATH}}) + @tty = interactive_fzy(input: input20) @tty.assert_matches ">\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10" - @tty = TTYtest.new_terminal(%{seq 10 | #{FZY_PATH} -l 5}) + @tty = interactive_fzy(input: input10, args: "-l 5") @tty.assert_matches ">\n1\n2\n3\n4\n5" - @tty = TTYtest.new_terminal(%{seq 10 | #{FZY_PATH} --lines=5}) + @tty = interactive_fzy(input: input10, args: "--lines=5") @tty.assert_matches ">\n1\n2\n3\n4\n5" end def test_prompt - @tty = TTYtest.new_terminal(%{echo -n "" | #{FZY_PATH}}) + @tty = interactive_fzy @tty.send_keys("foo") @tty.assert_matches '> foo' - @tty = TTYtest.new_terminal(%{echo -n "" | #{FZY_PATH} -p 'C:\\'}) + @tty = interactive_fzy(args: "-p 'C:\\'") @tty.send_keys("foo") @tty.assert_matches 'C:\foo' - @tty = TTYtest.new_terminal(%{echo -n "" | #{FZY_PATH} --prompt="foo bar "}) + @tty = interactive_fzy(args: "--prompt=\"foo bar \"") @tty.send_keys("baz") @tty.assert_matches "foo bar baz" end def test_show_scores expected_score = '( inf)' - @tty = TTYtest.new_terminal(%{echo -n "foo\nbar" | #{FZY_PATH} -s}) + @tty = interactive_fzy(input: %w[foo bar], args: "-s") @tty.send_keys('foo') @tty.assert_matches "> foo\n#{expected_score} foo" - @tty = TTYtest.new_terminal(%{echo -n "foo\nbar" | #{FZY_PATH} --show-scores}) + @tty = interactive_fzy(input: %w[foo bar], args: "--show-scores") @tty.send_keys('foo') @tty.assert_matches "> foo\n#{expected_score} foo" expected_score = '( 0.89)' - @tty = TTYtest.new_terminal(%{echo -n "foo\nbar" | #{FZY_PATH} -s}) + @tty = interactive_fzy(input: %w[foo bar], args: "-s") @tty.send_keys('f') @tty.assert_matches "> f\n#{expected_score} foo" end @@ -258,7 +258,7 @@ end def test_worker_count - @tty = TTYtest.new_terminal(%{echo -n "foo\nbar" | #{FZY_PATH} -j1}) + @tty = interactive_fzy(input: %w[foo bar], args: "-j1") @tty.send_keys('foo') @tty.assert_matches "> foo\nfoo" @@ -272,22 +272,180 @@ end def test_initial_query - @tty = TTYtest.new_terminal(%{echo -n "foo\nbar" | #{FZY_PATH} -q fo}) + @tty = interactive_fzy(input: %w[foo bar], args: "-q fo") @tty.assert_matches "> fo\nfoo" @tty.send_keys("o") @tty.assert_matches "> foo\nfoo" @tty.send_keys("o") @tty.assert_matches "> fooo" - @tty = TTYtest.new_terminal(%{echo -n "foo\nbar" | #{FZY_PATH} -q asdf}) + @tty = interactive_fzy(input: %w[foo bar], args: "-q asdf") @tty.assert_matches "> asdf" end def test_non_interactive - @tty = TTYtest.new_terminal(%{echo before; echo -n "foo\nbar" | #{FZY_PATH} -e foo; echo after}) + @tty = interactive_fzy(input: %w[foo bar], args: "-e foo", before: "before", after: "after") @tty.assert_matches "before\nfoo\nafter" end + def test_moving_text_cursor + @tty = interactive_fzy(input: %w[foo bar]) + @tty.send_keys("br") + @tty.assert_matches "> br\nbar" + @tty.assert_cursor_position(y: 0, x: 4) + + @tty.send_keys(LEFT) + @tty.assert_cursor_position(y: 0, x: 3) + @tty.assert_matches "> br\nbar" + @tty.send_keys("a") + @tty.assert_cursor_position(y: 0, x: 4) + @tty.assert_matches "> bar\nbar" + + @tty.send_keys(ctrl("A")) # Ctrl-A + @tty.assert_cursor_position(y: 0, x: 2) + @tty.assert_matches "> bar\nbar" + @tty.send_keys("foo") + @tty.assert_cursor_position(y: 0, x: 5) + @tty.assert_matches "> foobar" + + @tty.send_keys(ctrl("E")) # Ctrl-E + @tty.assert_cursor_position(y: 0, x: 8) + @tty.assert_matches "> foobar" + @tty.send_keys("baz") # Ctrl-E + @tty.assert_cursor_position(y: 0, x: 11) + @tty.assert_matches "> foobarbaz" + end + + # More info; + # https://github.com/jhawthorn/fzy/issues/42 + # https://cirw.in/blog/bracketed-paste + def test_bracketed_paste_characters + @tty = interactive_fzy(input: %w[foo bar]) + @tty.assert_matches ">\nfoo\nbar" + @tty.send_keys("\e[200~foo\e[201~") + @tty.assert_matches "> foo\nfoo" + end + + # https://github.com/jhawthorn/fzy/issues/81 + def test_slow_stdin_fast_user + @tty = TTYtest.new_terminal(%{(sleep 0.5; echo aa; echo bc; echo bd) | #{FZY_PATH}}) + + # Before input has all come in, but wait for fzy to at least start + sleep 0.1 + + @tty.send_keys("b\r") + @tty.assert_matches "bc" + end + + def test_unicode + @tty = interactive_fzy(input: %w[English Français 日本語]) + @tty.assert_matches <<~TTY + > + English + Français + 日本語 + TTY + @tty.assert_cursor_position(y: 0, x: 2) + + @tty.send_keys("ç") + @tty.assert_matches <<~TTY + > ç + Français + TTY + @tty.assert_cursor_position(y: 0, x: 3) + + @tty.send_keys("\r") + @tty.assert_matches "Français" + end + + def test_unicode_backspace + @tty = interactive_fzy + @tty.send_keys "Français" + @tty.assert_matches "> Français" + @tty.assert_cursor_position(y: 0, x: 10) + + @tty.send_keys(ctrl('H') * 3) + @tty.assert_matches "> Franç" + @tty.assert_cursor_position(y: 0, x: 7) + + @tty.send_keys(ctrl('H')) + @tty.assert_matches "> Fran" + @tty.assert_cursor_position(y: 0, x: 6) + + @tty.send_keys('ce') + @tty.assert_matches "> France" + + @tty = interactive_fzy + @tty.send_keys "日本語" + @tty.assert_matches "> 日本語" + @tty.send_keys(ctrl('H')) + @tty.assert_matches "> 日本" + @tty.send_keys(ctrl('H')) + @tty.assert_matches "> 日" + @tty.send_keys(ctrl('H')) + @tty.assert_matches "> " + @tty.assert_cursor_position(y: 0, x: 2) + end + + def test_unicode_delete_word + @tty = interactive_fzy + @tty.send_keys "Je parle Français" + @tty.assert_matches "> Je parle Français" + @tty.assert_cursor_position(y: 0, x: 19) + + @tty.send_keys(ctrl('W')) + @tty.assert_matches "> Je parle" + @tty.assert_cursor_position(y: 0, x: 11) + + @tty = interactive_fzy + @tty.send_keys "日本語" + @tty.assert_matches "> 日本語" + @tty.send_keys(ctrl('W')) + @tty.assert_matches "> " + @tty.assert_cursor_position(y: 0, x: 2) + end + + def test_unicode_cursor_movement + @tty = interactive_fzy + @tty.send_keys "Français" + @tty.assert_cursor_position(y: 0, x: 10) + + @tty.send_keys(LEFT*5) + @tty.assert_cursor_position(y: 0, x: 5) + + @tty.send_keys(RIGHT*3) + @tty.assert_cursor_position(y: 0, x: 8) + + @tty = interactive_fzy + @tty.send_keys "日本語" + @tty.assert_matches "> 日本語" + @tty.assert_cursor_position(y: 0, x: 8) + @tty.send_keys(LEFT) + @tty.assert_cursor_position(y: 0, x: 6) + @tty.send_keys(LEFT) + @tty.assert_cursor_position(y: 0, x: 4) + @tty.send_keys(LEFT) + @tty.assert_cursor_position(y: 0, x: 2) + @tty.send_keys(LEFT) + @tty.assert_cursor_position(y: 0, x: 2) + @tty.send_keys(RIGHT*3) + @tty.assert_cursor_position(y: 0, x: 8) + @tty.send_keys(RIGHT) + @tty.assert_cursor_position(y: 0, x: 8) + end + + def test_long_strings + ascii = "LongStringOfText" * 6 + unicode = "LongStringOfText" * 3 + + @tty = interactive_fzy(input: [ascii, unicode]) + @tty.assert_matches <<~TTY + > + LongStringOfTextLongStringOfTextLongStringOfTextLongStringOfTextLongStringOfText + LongStringOfTextLongStringOfTextLongStri + TTY + end + def test_help @tty = TTYtest.new_terminal(%{#{FZY_PATH} --help}) @tty.assert_matches <