From 13247c742d1ccd58c0523c31fcbc077cad1753b3 Mon Sep 17 00:00:00 2001 From: leo-arch Date: Sun, 17 Apr 2022 04:04:05 -0300 Subject: [PATCH 01/28] Update manpage --- fzy.1 | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/fzy.1 b/fzy.1 index 9c34a21..1c88231 100644 --- a/fzy.1 +++ b/fzy.1 @@ -25,6 +25,14 @@ How many lines of items to display. If unspecified, defaults to 10 lines. Input prompt (default: '> ') . .TP +.BR \-P ", " \-\-pad =\fINUM\fR +Left pad the list of matches NUM places (default: 0) +. +.TP +.BR \-m ", " \-\-multi +Enable multi-selection +. +.TP .BR \-s ", " \-\-show-scores Show the scores for each item. . @@ -56,28 +64,28 @@ Usage help. . .TP .BR "ENTER" -Print the selected item to stdout and exit +Print the selected items to stdout and exit. .TP .BR "Ctrl+c, Ctrl+g, Esc" Exit with status 1, without making a selection. .TP .BR "Up Arrow, Ctrl+p, Ctrl+k" -Select the previous item +Select the previous item. .TP .BR "Down Arrow, Ctrl+n, Ctrl+j" -Select the next item +Select the next item. .TP -Tab -Replace the current search string with the selected item +.BR "TAB" +Replace the current search string with the selected item. If the multi-selection mode (\fI-m, --multi\fR) is enabled, TAB is used to (un)mark the selected entry instead. .TP .BR "Backspace, Ctrl+h" -Delete the character before the cursor +Delete the character before the cursor. .TP .BR Ctrl+w -Delete the word before the cursor +Delete the word before the cursor. .TP .BR Ctrl+u -Delete the entire line +Delete the entire line. . .SH USAGE EXAMPLES . From ccc6ff9ac9c981235716dce2da152dab1aed8987 Mon Sep 17 00:00:00 2001 From: leo-arch Date: Sun, 17 Apr 2022 04:04:31 -0300 Subject: [PATCH 02/28] Add multi-select and list padding --- src/choices.c | 10 +++ src/options.c | 32 +++++++-- src/options.h | 2 + src/tty_interface.c | 156 ++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 188 insertions(+), 12 deletions(-) diff --git a/src/choices.c b/src/choices.c index fe2f80b..8b04267 100644 --- a/src/choices.c +++ b/src/choices.c @@ -265,13 +265,23 @@ void choices_search(choices_t *c, const char *search) { choices_reset_search(c); struct search_job *job = calloc(1, sizeof(struct search_job)); + if (!job) { + fprintf(stderr, "Error: Can't allocate memory\n"); + abort(); + } + job->search = search; job->choices = c; if (pthread_mutex_init(&job->lock, NULL) != 0) { fprintf(stderr, "Error: pthread_mutex_init failed\n"); abort(); } + job->workers = calloc(c->worker_count, sizeof(struct worker)); + if (!job->workers) { + fprintf(stderr, "Error: Can't allocate memory\n"); + abort(); + } struct worker *workers = job->workers; for (int i = c->worker_count - 1; i >= 0; i--) { diff --git a/src/options.c b/src/options.c index e35402f..3c25fea 100644 --- a/src/options.c +++ b/src/options.c @@ -12,7 +12,9 @@ static const char *usage_str = "" "Usage: fzy [OPTION]...\n" " -l, --lines=LINES Specify how many lines of results to show (default 10)\n" + " -m, --multi Enable multi-selection\n" " -p, --prompt=PROMPT Input prompt (default '> ')\n" + " -P, --pad=NUM Left pad list of matches NUM places (default 2)\n" " -q, --query=QUERY Use QUERY as the initial search string\n" " -e, --show-matches=QUERY Output the sorted matches of QUERY\n" " -t, --tty=TTY Specify file to use as TTY device (default /dev/tty)\n" @@ -20,14 +22,15 @@ static const char *usage_str = " -0, --read-null Read input delimited by ASCII NUL characters\n" " -j, --workers NUM Use NUM workers for searching. (default is # of CPUs)\n" " -i, --show-info Show selection info line\n" - " -h, --help Display this help and exit\n" - " -v, --version Output version information and exit\n"; + " -h, --help Display this help and exit\n" + " -v, --version Output version information and exit\n"; static void usage(const char *argv0) { fprintf(stderr, usage_str, argv0); } -static struct option longopts[] = {{"show-matches", required_argument, NULL, 'e'}, +static struct option longopts[] = { + {"show-matches", required_argument, NULL, 'e'}, {"query", required_argument, NULL, 'q'}, {"lines", required_argument, NULL, 'l'}, {"tty", required_argument, NULL, 't'}, @@ -39,7 +42,10 @@ static struct option longopts[] = {{"show-matches", required_argument, NULL, 'e' {"workers", required_argument, NULL, 'j'}, {"show-info", no_argument, NULL, 'i'}, {"help", no_argument, NULL, 'h'}, - {NULL, 0, NULL, 0}}; + {"pad", required_argument, NULL, 'P'}, + {"multi", no_argument, NULL, 'm'}, + {NULL, 0, NULL, 0} +}; void options_init(options_t *options) { /* set defaults */ @@ -54,16 +60,18 @@ void options_init(options_t *options) { options->workers = DEFAULT_WORKERS; options->input_delimiter = '\n'; options->show_info = DEFAULT_SHOW_INFO; + options->pad = 2; + options->multi = 0; } void options_parse(options_t *options, int argc, char *argv[]) { options_init(options); int c; - while ((c = getopt_long(argc, argv, "vhs0e:q:l:t:p:j:i", longopts, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "mvhs0e:q:l:t:p:P:j:i", longopts, NULL)) != -1) { switch (c) { case 'v': - printf("%s " VERSION " © 2014-2018 John Hawthorn\n", argv[0]); +// printf("%s " VERSION " © 2014-2018 John Hawthorn\n", argv[0]); exit(EXIT_SUCCESS); case 's': options->show_scores = 1; @@ -71,6 +79,9 @@ void options_parse(options_t *options, int argc, char *argv[]) { case '0': options->input_delimiter = '\0'; break; + case 'm': + options->multi = 1; + break; case 'q': options->init_search = optarg; break; @@ -93,6 +104,10 @@ void options_parse(options_t *options, int argc, char *argv[]) { case 'p': options->prompt = optarg; break; + case 'P': + if (optarg) + options->pad = atoi(optarg); + break; case 'j': if (sscanf(optarg, "%u", &options->workers) != 1) { usage(argv[0]); @@ -100,6 +115,8 @@ void options_parse(options_t *options, int argc, char *argv[]) { } break; case 'l': { + if (!optarg) + break; int l; if (!strcmp(optarg, "max")) { l = INT_MAX; @@ -114,7 +131,8 @@ void options_parse(options_t *options, int argc, char *argv[]) { case 'i': options->show_info = 1; break; - case 'h': + + case 'h': /* fallthrough */ default: usage(argv[0]); exit(EXIT_SUCCESS); diff --git a/src/options.h b/src/options.h index 4be4cb6..21ca627 100644 --- a/src/options.h +++ b/src/options.h @@ -13,6 +13,8 @@ typedef struct { unsigned int workers; char input_delimiter; int show_info; + int pad; + int multi; } options_t; void options_init(options_t *options); diff --git a/src/tty_interface.c b/src/tty_interface.c index 343dde8..2f3cd0f 100644 --- a/src/tty_interface.c +++ b/src/tty_interface.c @@ -7,6 +7,120 @@ #include "tty_interface.h" #include "../config.h" +/* An array numbers to store the index of selected entries (choices) */ +static size_t *selection_marks = (size_t *)NULL; + +/* Create a size_t array big enough to hold all availalable choices, + * and initialize it to zero */ +static void +init_selection_marks(tty_interface_t *state) +{ + if (state->options->multi == 0) { + return; + } + + selection_marks = (size_t *)malloc(state->choices->available * sizeof(size_t)); + for (size_t i = 0; i < state->choices->available; i++) { + selection_marks[i] = 0; + } +} + +/* Return the amount of selected entries in the list of matches */ +static size_t +count_selections(tty_interface_t *state) +{ + size_t c = 0; + for (size_t i = 0; i < state->choices->available; i++) { + if (selection_marks[i] == 1) { + c++; + } + } + + return c; +} + +/* Store current selected entries into an array to print each of them + * at exit. Return this array of strings if success and NULL in case + * of error or no selected entries */ +static char ** +save_selections(tty_interface_t *state) +{ + size_t i, c = 0; + size_t n = count_selections(state); + if (n == 0) { + return (char **)NULL; + } + + char **s = (char **)malloc((n + 2) * sizeof(char *)); + + for (i = 0; i < state->choices->available; i++) { + if (selection_marks[i] != 1) { + continue; + } + + const char *name = choices_get(state->choices, i); + if (!name) { + continue; + } + + s[c] = (char *)malloc((strlen(name) + 1) * sizeof(char)); + strcpy(s[c], name); + c++; + + if (c == n) { + break; + } + } + + if (c == 0) { + free(s); + return (char **)NULL; + } + + s[c] = (char *)NULL; + return s; +} + +/* Free the array of selected entries generated by save_selections() */ +static void +free_selections(char **s) +{ + size_t i; + for (i = 0; s[i]; i++) { + free(s[i]); + } + free(s); +} + +/* Print the list of selected entries generated by save_selections() */ +static void +print_selections(char **s) +{ + size_t i; + for (i = 0; s[i]; i++) { + printf("%s\n", s[i]); + } +} + +/* Free the size_t array generated by init_selection_marks() */ +static void +free_selection_marks(tty_interface_t *state) +{ + if (state->options->multi == 1) { + free(selection_marks); + selection_marks = (size_t *)NULL; + } +} + +/* Set to 1 the index of the current/selected entry in the selection_marks + * array. If this entry is already marked/selected, set it to zero */ +static void +action_select(tty_interface_t *state) +{ + size_t s = selection_marks[state->choices->selection]; + selection_marks[state->choices->selection] = s == 1 ? 0 : 1; +} + static int isprint_unicode(char c) { return isprint(c) || c & (1 << 7); } @@ -18,7 +132,7 @@ static int is_boundary(char c) { static void clear(tty_interface_t *state) { tty_t *tty = state->tty; - tty_setcol(tty, 0); + tty_setcol(tty, state->options->pad); size_t line = 0; while (line++ < state->options->num_lines + (state->options->show_info ? 1 : 0)) { tty_newline(tty); @@ -91,7 +205,7 @@ static void draw(tty_interface_t *state) { } } - tty_setcol(tty, 0); + tty_setcol(tty, options->pad); tty_printf(tty, "%s%s", options->prompt, state->search); tty_clearline(tty); @@ -105,6 +219,11 @@ static void draw(tty_interface_t *state) { tty_clearline(tty); const char *choice = choices_get(choices, i); if (choice) { + if (options->multi == 1 && selection_marks[i] == 1) { + tty_printf(tty, "%*c %s", options->pad - 1, '*', ""); + } else { + tty_printf(tty, "%*s", options->pad, ""); + } draw_match(state, choice, i == choices->selection); } } @@ -112,7 +231,7 @@ static void draw(tty_interface_t *state) { if (num_lines + options->show_info) tty_moveup(tty, num_lines + options->show_info); - tty_setcol(tty, 0); + tty_setcol(tty, options->pad); fputs(options->prompt, tty->fout); for (size_t i = 0; i < state->cursor; i++) fputc(state->search[i], tty->fout); @@ -134,6 +253,22 @@ static void update_state(tty_interface_t *state) { static void action_emit(tty_interface_t *state) { update_state(state); + if (state->options->multi == 1) { + char **s = save_selections(state); + free_selection_marks(state); + + clear(state); + tty_close(state->tty); + + if (s) { + print_selections(s); + free_selections(s); + } + + state->exit = EXIT_SUCCESS; + return; + } + /* Reset the tty as close as possible to the previous state */ clear(state); @@ -236,6 +371,12 @@ static void action_pagedown(tty_interface_t *state) { } static void action_autocomplete(tty_interface_t *state) { + if (state->options->multi == 1) { + action_select(state); + action_next(state); + return; + } + update_state(state); const char *current_selection = choices_get(state->choices, state->choices->selection); if (current_selection) { @@ -372,6 +513,7 @@ static void handle_input(tty_interface_t *state, const char *s, int handle_ambig } int tty_interface_run(tty_interface_t *state) { + init_selection_marks(state); draw(state); for (;;) { @@ -384,8 +526,10 @@ int tty_interface_run(tty_interface_t *state) { char s[2] = {tty_getchar(state->tty), '\0'}; handle_input(state, s, 0); - if (state->exit >= 0) + if (state->exit >= 0) { + free_selection_marks(state); return state->exit; + } draw(state); } while (tty_input_ready(state->tty, state->ambiguous_key_pending ? KEYTIMEOUT : 0, 0)); @@ -394,8 +538,10 @@ int tty_interface_run(tty_interface_t *state) { char s[1] = ""; handle_input(state, s, 1); - if (state->exit >= 0) + if (state->exit >= 0) { + free_selection_marks(state); return state->exit; + } } update_state(state); From 4a000a5b0292ef1e924b71727d124481c02f2cf7 Mon Sep 17 00:00:00 2001 From: leo-arch Date: Sun, 17 Apr 2022 05:17:20 -0300 Subject: [PATCH 03/28] Rewrote selections code --- src/tty_interface.c | 158 +++++++++++++++++++------------------------- 1 file changed, 69 insertions(+), 89 deletions(-) diff --git a/src/tty_interface.c b/src/tty_interface.c index 2f3cd0f..8822ab6 100644 --- a/src/tty_interface.c +++ b/src/tty_interface.c @@ -7,118 +7,103 @@ #include "tty_interface.h" #include "../config.h" -/* An array numbers to store the index of selected entries (choices) */ -static size_t *selection_marks = (size_t *)NULL; +static char **selections = (char **)NULL; +static size_t seln = 0, sel_counter = 0; -/* Create a size_t array big enough to hold all availalable choices, - * and initialize it to zero */ -static void -init_selection_marks(tty_interface_t *state) +static int +is_selected(const char *p) { - if (state->options->multi == 0) { - return; + if (!p || !*p || sel_counter == 0) { + return 0; } - selection_marks = (size_t *)malloc(state->choices->available * sizeof(size_t)); - for (size_t i = 0; i < state->choices->available; i++) { - selection_marks[i] = 0; - } -} - -/* Return the amount of selected entries in the list of matches */ -static size_t -count_selections(tty_interface_t *state) -{ - size_t c = 0; - for (size_t i = 0; i < state->choices->available; i++) { - if (selection_marks[i] == 1) { - c++; + size_t i; + for (i = 0; selections[i]; i++) { + if (*selections[i] == *p && strcmp(selections[i], p) == 0) { + return 1; } } - return c; + return 0; } -/* Store current selected entries into an array to print each of them - * at exit. Return this array of strings if success and NULL in case - * of error or no selected entries */ -static char ** -save_selections(tty_interface_t *state) +static void +deselect_entry(char *name) { - size_t i, c = 0; - size_t n = count_selections(state); - if (n == 0) { - return (char **)NULL; + if (!name || !*name || sel_counter == 0) { + return; } - char **s = (char **)malloc((n + 2) * sizeof(char *)); - - for (i = 0; i < state->choices->available; i++) { - if (selection_marks[i] != 1) { - continue; - } - - const char *name = choices_get(state->choices, i); - if (!name) { + size_t i; + for (i = 0; selections[i]; i++) { + if (*selections[i] != *name || strcmp(selections[i], name) != 0) { continue; } + *selections[i] = '\0'; + sel_counter--; + break; + } +} - s[c] = (char *)malloc((strlen(name) + 1) * sizeof(char)); - strcpy(s[c], name); - c++; +static void +save_selection(const char *p) +{ + selections = (char **)realloc(selections, (seln + 2) * sizeof(char *)); + selections[seln] = (char *)malloc((strlen(p) + 1) * sizeof(char)); + strcpy(selections[seln], p); + seln++; + sel_counter++; + selections[seln] = (char *)NULL; +} - if (c == n) { - break; - } +static int +action_select(tty_interface_t *state) +{ + const char *p = choices_get(state->choices, state->choices->selection); + if (!p) { + return EXIT_FAILURE; } - if (c == 0) { - free(s); - return (char **)NULL; + if (is_selected(p) == 1) { + deselect_entry((char *)p); + return EXIT_FAILURE; } - s[c] = (char *)NULL; - return s; + save_selection(p); + + return EXIT_SUCCESS; } -/* Free the array of selected entries generated by save_selections() */ static void -free_selections(char **s) +print_selections(tty_interface_t *state) { - size_t i; - for (i = 0; s[i]; i++) { - free(s[i]); + if (sel_counter == 0 || state->options->multi == 0) { + return; } - free(s); -} -/* Print the list of selected entries generated by save_selections() */ -static void -print_selections(char **s) -{ size_t i; - for (i = 0; s[i]; i++) { - printf("%s\n", s[i]); + for (i = 0; selections[i]; i++) { + if (!*selections[i]) { + continue; + } + printf("%s\n", selections[i]); } + } -/* Free the size_t array generated by init_selection_marks() */ static void -free_selection_marks(tty_interface_t *state) +free_selections(tty_interface_t *state) { - if (state->options->multi == 1) { - free(selection_marks); - selection_marks = (size_t *)NULL; + if (state->options->multi == 0) { + return; } -} -/* Set to 1 the index of the current/selected entry in the selection_marks - * array. If this entry is already marked/selected, set it to zero */ -static void -action_select(tty_interface_t *state) -{ - size_t s = selection_marks[state->choices->selection]; - selection_marks[state->choices->selection] = s == 1 ? 0 : 1; + size_t i; + for (i = 0; selections[i]; i++) { + free(selections[i]); + } + free(selections); + selections = (char **)NULL; } static int isprint_unicode(char c) { @@ -219,7 +204,7 @@ static void draw(tty_interface_t *state) { tty_clearline(tty); const char *choice = choices_get(choices, i); if (choice) { - if (options->multi == 1 && selection_marks[i] == 1) { + if (options->multi == 1 && is_selected((char *)choice)) { tty_printf(tty, "%*c %s", options->pad - 1, '*', ""); } else { tty_printf(tty, "%*s", options->pad, ""); @@ -254,16 +239,11 @@ static void action_emit(tty_interface_t *state) { update_state(state); if (state->options->multi == 1) { - char **s = save_selections(state); - free_selection_marks(state); - clear(state); tty_close(state->tty); - if (s) { - print_selections(s); - free_selections(s); - } + print_selections(state); + free_selections(state); state->exit = EXIT_SUCCESS; return; @@ -513,7 +493,7 @@ static void handle_input(tty_interface_t *state, const char *s, int handle_ambig } int tty_interface_run(tty_interface_t *state) { - init_selection_marks(state); +// init_selection_marks(state); draw(state); for (;;) { @@ -527,7 +507,7 @@ int tty_interface_run(tty_interface_t *state) { handle_input(state, s, 0); if (state->exit >= 0) { - free_selection_marks(state); + free_selections(state); return state->exit; } @@ -539,7 +519,7 @@ int tty_interface_run(tty_interface_t *state) { handle_input(state, s, 1); if (state->exit >= 0) { - free_selection_marks(state); + free_selections(state); return state->exit; } } From db015dc5ffd8419a6985f4963e465e652a04dfdb Mon Sep 17 00:00:00 2001 From: leo-arch Date: Sun, 17 Apr 2022 05:28:13 -0300 Subject: [PATCH 04/28] Add comments to new functions --- src/tty_interface.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/tty_interface.c b/src/tty_interface.c index 8822ab6..452314e 100644 --- a/src/tty_interface.c +++ b/src/tty_interface.c @@ -7,9 +7,15 @@ #include "tty_interface.h" #include "../config.h" +/* Array to store selected/marked entries */ static char **selections = (char **)NULL; + +/* SEL_N is the current size of the selections array, while SEL_COUNTER + * is the current amount of actually selected entries */ static size_t seln = 0, sel_counter = 0; +/* Search for the string P in the selections array. If found, return 1, + * otherwise zero */ static int is_selected(const char *p) { @@ -27,6 +33,8 @@ is_selected(const char *p) return 0; } +/* Remote the entry NAME from the selections array by setting the first + * byte of the corresponding array entry to NUL */ static void deselect_entry(char *name) { @@ -45,6 +53,7 @@ deselect_entry(char *name) } } +/* Save the string P into the selections array */ static void save_selection(const char *p) { @@ -56,6 +65,8 @@ save_selection(const char *p) selections[seln] = (char *)NULL; } +/* Select the currently highighted/hovered entry if not already selected. + * Otherwise, remove it from the selections list */ static int action_select(tty_interface_t *state) { @@ -74,6 +85,7 @@ action_select(tty_interface_t *state) return EXIT_SUCCESS; } +/* Print the list of selected/marked entries to STDOUT */ static void print_selections(tty_interface_t *state) { @@ -91,10 +103,11 @@ print_selections(tty_interface_t *state) } +/* Free the selections array */ static void free_selections(tty_interface_t *state) { - if (state->options->multi == 0) { + if (state->options->multi == 0 || sel_n == 0) { return; } From 0a8f6cad8cc25947dd7edad32d9a9771eaabc055 Mon Sep 17 00:00:00 2001 From: leo-arch Date: Sun, 17 Apr 2022 05:30:01 -0300 Subject: [PATCH 05/28] Fix wrong var name --- src/tty_interface.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tty_interface.c b/src/tty_interface.c index 452314e..125a790 100644 --- a/src/tty_interface.c +++ b/src/tty_interface.c @@ -107,7 +107,7 @@ print_selections(tty_interface_t *state) static void free_selections(tty_interface_t *state) { - if (state->options->multi == 0 || sel_n == 0) { + if (state->options->multi == 0 || seln == 0) { return; } From 5d5129416996a89e19a032efae2c86b31bcf442c Mon Sep 17 00:00:00 2001 From: leo-arch Date: Sun, 17 Apr 2022 16:27:13 -0300 Subject: [PATCH 06/28] Update manpage --- fzy.1 | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/fzy.1 b/fzy.1 index 1c88231..6d4727f 100644 --- a/fzy.1 +++ b/fzy.1 @@ -60,6 +60,18 @@ Usage help. .BR \-v ", " \-\-version Usage help. . +.TP +.BR \-\-pointer =\fICHAR\fR +Pointer to highlightled match (default '>') +. +.TP +.BR \-\-marker =\fICHAR\fR +Multi-select marker (default '*') +. +.TP +.BR \-\-cyclic +Enable cyclic scrolling +. .SH KEYS . .TP From 0eddf61a1a6d3633acebd1615b7a79a0ceef526c Mon Sep 17 00:00:00 2001 From: leo-arch Date: Sun, 17 Apr 2022 16:28:03 -0300 Subject: [PATCH 07/28] Add pointer, marker, and cycle options --- src/options.c | 44 ++++++++++++++++++++++++++++++++------------ src/options.h | 3 +++ src/tty_interface.c | 14 +++++++++----- 3 files changed, 44 insertions(+), 17 deletions(-) diff --git a/src/options.c b/src/options.c index 3c25fea..04b079e 100644 --- a/src/options.c +++ b/src/options.c @@ -14,7 +14,7 @@ static const char *usage_str = " -l, --lines=LINES Specify how many lines of results to show (default 10)\n" " -m, --multi Enable multi-selection\n" " -p, --prompt=PROMPT Input prompt (default '> ')\n" - " -P, --pad=NUM Left pad list of matches NUM places (default 2)\n" + " -P, --pad=NUM Left pad the list of matches NUM places (default 0)\n" " -q, --query=QUERY Use QUERY as the initial search string\n" " -e, --show-matches=QUERY Output the sorted matches of QUERY\n" " -t, --tty=TTY Specify file to use as TTY device (default /dev/tty)\n" @@ -23,7 +23,10 @@ static const char *usage_str = " -j, --workers NUM Use NUM workers for searching. (default is # of CPUs)\n" " -i, --show-info Show selection info line\n" " -h, --help Display this help and exit\n" - " -v, --version Output version information and exit\n"; + " -v, --version Output version information and exit\n" + " --pointer Pointer to highlighted match (default '>')\n" + " --marker Multi-select marker (default '*')\n" + " --cycle Enable cyclic scrolling\n"; static void usage(const char *argv0) { fprintf(stderr, usage_str, argv0); @@ -44,24 +47,30 @@ static struct option longopts[] = { {"help", no_argument, NULL, 'h'}, {"pad", required_argument, NULL, 'P'}, {"multi", no_argument, NULL, 'm'}, + {"pointer", required_argument, NULL, 1}, + {"marker", required_argument, NULL, 2}, + {"cycle", no_argument, NULL, 3}, {NULL, 0, NULL, 0} }; void options_init(options_t *options) { /* set defaults */ - options->benchmark = 0; - options->filter = NULL; - options->init_search = NULL; - options->show_scores = 0; - options->scrolloff = 1; + options->benchmark = DEFAULT_BENCHMARK; + options->filter = DEFAULT_FILTER; + options->init_search = DEFAULT_INIT_SEARCH; + options->show_scores = DEFAULT_SCORES; + options->scrolloff = DEFAULT_SCROLLOFF; options->tty_filename = DEFAULT_TTY; options->num_lines = DEFAULT_NUM_LINES; options->prompt = DEFAULT_PROMPT; options->workers = DEFAULT_WORKERS; - options->input_delimiter = '\n'; + options->input_delimiter = DEFAULT_DELIMITER; options->show_info = DEFAULT_SHOW_INFO; - options->pad = 2; - options->multi = 0; + options->pad = DEFAULT_PAD; + options->multi = DEFAULT_MULTI; + options->pointer = DEFAULT_POINTER; + options->marker = DEFAULT_MARKER; + options->cycle = DEFAULT_CYCLE; } void options_parse(options_t *options, int argc, char *argv[]) { @@ -71,7 +80,7 @@ void options_parse(options_t *options, int argc, char *argv[]) { while ((c = getopt_long(argc, argv, "mvhs0e:q:l:t:p:P:j:i", longopts, NULL)) != -1) { switch (c) { case 'v': -// printf("%s " VERSION " © 2014-2018 John Hawthorn\n", argv[0]); + printf("%s " VERSION " © 2014-2018 John Hawthorn\n", argv[0]); exit(EXIT_SUCCESS); case 's': options->show_scores = 1; @@ -105,7 +114,7 @@ void options_parse(options_t *options, int argc, char *argv[]) { options->prompt = optarg; break; case 'P': - if (optarg) + if (optarg && *optarg && *optarg >= '0' && *optarg <= '9') options->pad = atoi(optarg); break; case 'j': @@ -131,6 +140,17 @@ void options_parse(options_t *options, int argc, char *argv[]) { case 'i': options->show_info = 1; break; + case 1: + if (optarg && *optarg) + options->pointer = *optarg; + break; + case 2: + if (optarg && *optarg) + options->marker = *optarg; + break; + case 3: + options->cycle = 1; + break; case 'h': /* fallthrough */ default: diff --git a/src/options.h b/src/options.h index 21ca627..fcb4623 100644 --- a/src/options.h +++ b/src/options.h @@ -15,6 +15,9 @@ typedef struct { int show_info; int pad; int multi; + char pointer; + char marker; + int cycle; } options_t; void options_init(options_t *options); diff --git a/src/tty_interface.c b/src/tty_interface.c index 125a790..09f41ee 100644 --- a/src/tty_interface.c +++ b/src/tty_interface.c @@ -217,11 +217,10 @@ static void draw(tty_interface_t *state) { tty_clearline(tty); const char *choice = choices_get(choices, i); if (choice) { - if (options->multi == 1 && is_selected((char *)choice)) { - tty_printf(tty, "%*c %s", options->pad - 1, '*', ""); - } else { - tty_printf(tty, "%*s", options->pad, ""); - } + int multi_sel = options->multi == 1 && is_selected((char *)choice); + tty_printf(tty, "%*s%c%c", options->pad, "", + i == choices->selection ? options->pointer : ' ', + multi_sel == 1 ? options->marker : ' '); draw_match(state, choice, i == choices->selection); } } @@ -314,6 +313,8 @@ static void action_del_all(tty_interface_t *state) { } static void action_prev(tty_interface_t *state) { + if (state->options->cycle == 0 && state->choices->selection == 0) + return; update_state(state); choices_prev(state->choices); } @@ -323,6 +324,9 @@ static void action_ignore(tty_interface_t *state) { } static void action_next(tty_interface_t *state) { + if (state->options->cycle == 0 + && state->choices->selection + 1 >= state->choices->available) + return; update_state(state); choices_next(state->choices); } From 407ecfe4ee7af3f69638d46ee1f47b62a6e7365a Mon Sep 17 00:00:00 2001 From: leo-arch Date: Sun, 17 Apr 2022 18:23:42 -0300 Subject: [PATCH 08/28] Fix double free --- src/tty_interface.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/tty_interface.c b/src/tty_interface.c index 09f41ee..5ee685a 100644 --- a/src/tty_interface.c +++ b/src/tty_interface.c @@ -107,7 +107,7 @@ print_selections(tty_interface_t *state) static void free_selections(tty_interface_t *state) { - if (state->options->multi == 0 || seln == 0) { + if (state->options->multi == 0 || seln == 0 || !selections) { return; } @@ -256,7 +256,6 @@ static void action_emit(tty_interface_t *state) { print_selections(state); free_selections(state); - state->exit = EXIT_SUCCESS; return; } @@ -510,7 +509,6 @@ static void handle_input(tty_interface_t *state, const char *s, int handle_ambig } int tty_interface_run(tty_interface_t *state) { -// init_selection_marks(state); draw(state); for (;;) { From 3f14985366c27298bbccda61223504f87c1a6106 Mon Sep 17 00:00:00 2001 From: leo-arch Date: Sun, 17 Apr 2022 18:37:03 -0300 Subject: [PATCH 09/28] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index d053934..9502062 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ **fzy** is a fast, simple fuzzy text selector for the terminal with an advanced scoring algorithm. +**NOTE**: Compared to `fzy` 1.0, this fork adds the following features: **1)** left pad for the list of matches (`-P,--pad`), **2)** multi-selection (`-m,--multi`), **3)** pointer and marker customization (`--pointer`, and `--marker` respectivelly), and **4)** cyclic scrolling (`--cycle`, default to disabled). + [Try it out online!](http://jhawthorn.github.io/fzy-demo) ![](http://i.hawth.ca/u/fzy_animated_demo.svg) From bb4cdfdca7ef6b89ff92ba1d6d24dfb451cbf74e Mon Sep 17 00:00:00 2001 From: leo-arch Date: Sun, 17 Apr 2022 21:41:25 +0000 Subject: [PATCH 10/28] Update README.md --- README.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9502062..c3cb120 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,18 @@ **fzy** is a fast, simple fuzzy text selector for the terminal with an advanced scoring algorithm. -**NOTE**: Compared to `fzy` 1.0, this fork adds the following features: **1)** left pad for the list of matches (`-P,--pad`), **2)** multi-selection (`-m,--multi`), **3)** pointer and marker customization (`--pointer`, and `--marker` respectivelly), and **4)** cyclic scrolling (`--cycle`, default to disabled). +--- + +**NOTE** + +Compared to `fzy` 1.0, this fork adds the following features: + +**1)** Left pad for the list of matches (`-P,--pad`) \ +**2)** Multi-selection (`-m,--multi`) \ +**3)** Pointer and marker customization (`--pointer` and `--marker` respectivelly) \ +**4)** Cyclic scrolling (`--cycle`). Defaults to disabled + +--- [Try it out online!](http://jhawthorn.github.io/fzy-demo) From f23fb9a000d53cfb21bc37efdb80ea066d4e1cc5 Mon Sep 17 00:00:00 2001 From: leo-arch Date: Mon, 18 Apr 2022 02:22:25 -0300 Subject: [PATCH 11/28] Add color usage description --- fzy.1 | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/fzy.1 b/fzy.1 index 6d4727f..23c645c 100644 --- a/fzy.1 +++ b/fzy.1 @@ -72,6 +72,18 @@ Multi-select marker (default '*') .BR \-\-cyclic Enable cyclic scrolling . +.TP +.BR \-\-tab-accetps +TAB accepts: print selection and exit +. +.TP +.BR \-\-right-accepts +Right arrow key accepts: print selection and exit +. +.TP +.BR \-\-left-aborts +Left arrow key aborts: cancel selection and exit +. .SH KEYS . .TP @@ -99,6 +111,36 @@ Delete the word before the cursor. .BR Ctrl+u Delete the entire line. . +.SH COLORS +Interface colors are read from the environment variable \fBFZY_COLORS\fR using a simple pattern: the \fIorder\fR of the color code specifies which \fIinterface element\fR the color must be applied to, while the \fIcontent\fR of this code defines the \fIcolor\fR of this element. +.sp +\fBA\fR. The order is this: + 1) Prompt + 2) Pointer + 3) Marker + 4) Current entry foreground + 5) Current entry background +.sp +\fBB\fR. Possible content (available colors): + 0 = black + 1 = red + 2 = green + 3 = yellow + 4 = blue + 5 = magenta + 6 = cyan + 7 = white +.sp +Use a \fBb\fR before the color to make it bold/bright. +.sp +For example, \fBFZF_COLORS="-b1b2-4"\fR is to be read as follows: +.sp + \fB-\fR: no prompt color + \fBb1\fR: bold red pointer color + \fBb2\fR: bold green marker color + \fB-\fR: no color for the current entry foreground + \fB4\fR: blue current entry background +. .SH USAGE EXAMPLES . .TP From 5efa5b5b4db6e9911dfbc6640da241998df812b4 Mon Sep 17 00:00:00 2001 From: leo-arch Date: Mon, 18 Apr 2022 02:22:58 -0300 Subject: [PATCH 12/28] Add basic color support --- src/options.c | 20 ++++++++- src/options.h | 3 ++ src/tty_interface.c | 106 +++++++++++++++++++++++++++++++++++++------- 3 files changed, 113 insertions(+), 16 deletions(-) diff --git a/src/options.c b/src/options.c index 04b079e..9db07ce 100644 --- a/src/options.c +++ b/src/options.c @@ -26,7 +26,10 @@ static const char *usage_str = " -v, --version Output version information and exit\n" " --pointer Pointer to highlighted match (default '>')\n" " --marker Multi-select marker (default '*')\n" - " --cycle Enable cyclic scrolling\n"; + " --cycle Enable cyclic scrolling\n" + " --tab-accepts TAB accepts\n" + " --right-accepts Right arrow key accepts\n" + " --left-aborts Left arrow key aborts\n"; static void usage(const char *argv0) { fprintf(stderr, usage_str, argv0); @@ -50,6 +53,9 @@ static struct option longopts[] = { {"pointer", required_argument, NULL, 1}, {"marker", required_argument, NULL, 2}, {"cycle", no_argument, NULL, 3}, + {"tab-accepts", no_argument, NULL, 4}, + {"right-accepts", no_argument, NULL, 5}, + {"left-aborts", no_argument, NULL, 6}, {NULL, 0, NULL, 0} }; @@ -71,6 +77,9 @@ void options_init(options_t *options) { options->pointer = DEFAULT_POINTER; options->marker = DEFAULT_MARKER; options->cycle = DEFAULT_CYCLE; + options->tab_accepts = DEFAULT_TAB_ACCEPTS; + options->right_accepts = DEFAULT_RIGHT_ACCEPTS; + options->left_aborts = DEFAULT_LEFT_ABORTS; } void options_parse(options_t *options, int argc, char *argv[]) { @@ -151,6 +160,15 @@ void options_parse(options_t *options, int argc, char *argv[]) { case 3: options->cycle = 1; break; + case 4: + options->tab_accepts = 1; + break; + case 5: + options->right_accepts = 1; + break; + case 6: + options->left_aborts = 1; + break; case 'h': /* fallthrough */ default: diff --git a/src/options.h b/src/options.h index fcb4623..0297dc1 100644 --- a/src/options.h +++ b/src/options.h @@ -18,6 +18,9 @@ typedef struct { char pointer; char marker; int cycle; + int tab_accepts; + int right_accepts; + int left_aborts; } options_t; void options_init(options_t *options); diff --git a/src/tty_interface.c b/src/tty_interface.c index 5ee685a..99b3441 100644 --- a/src/tty_interface.c +++ b/src/tty_interface.c @@ -14,6 +14,55 @@ static char **selections = (char **)NULL; * is the current amount of actually selected entries */ static size_t seln = 0, sel_counter = 0; +static char colors[COLOR_ITEMS_NUM][MAX_COLOR_LEN]; +/* Parse colors taken from FZY_COLORS environment variable + * Colors are parsed in strict order (see config.h) + * Colors could be: 0-7 for normal colors, and b0-b7 for bold colors + * Specific colors could be skipped using a dash ('-'). + * Colors are stored in the COLORS array using the same order defined in + * config.h + * These colors are applied in draw() and draw_match() functions in this file + * + * For example, "-b1b2-4" is read as follows: + * -: no PROMPT color + * b1: bold red POINTER color + * b2: bold green MARKER color + * -: no SELECTED ENTRY FOREGROUND color + * 4: blue SELECTED ENTRY BACKGROUND color + * */ +static void +set_colors(void) +{ + char *p = getenv("NO_COLOR"); + if (p) + return; + + p = getenv("FZY_COLORS"); + if (!p || !*p) + return; + + size_t i, b = 0, c = 0; + for (i = 0; p[i] && c < COLOR_ITEMS_NUM; i++) { + if (p[i] == 'b') { + b = 1; + continue; + } + if ((p[i] < '0' || p[i] > '7') || p[i] == '-') { + *colors[c] = '\0'; + b = 0; + c++; + continue; + } + /* 16 colors: 0-7 normal; b0-b7 bright */ + snprintf(colors[c], MAX_COLOR_LEN, "\x1b[%s%c%cm", + b == 1 ? "1;" : "", + c == SEL_BG_COLOR ? '4' : '3', + p[i]); + b = 0; + c++; + } +} + /* Search for the string P in the selections array. If found, return 1, * otherwise zero */ static int @@ -162,12 +211,21 @@ static void draw_match(tty_interface_t *state, const char *choice, int selected) } } - if (selected) + if (selected) { #ifdef TTY_SELECTION_UNDERLINE tty_setunderline(tty); #else - tty_setinvert(tty); + /* Let's colorize the selected entry */ + if (*colors[SEL_FG_COLOR] || *colors[SEL_BG_COLOR]) { + if (*colors[SEL_FG_COLOR]) + tty_printf(tty, "%s", colors[SEL_FG_COLOR]); + if (*colors[SEL_BG_COLOR]) + tty_printf(tty, "%s", colors[SEL_BG_COLOR]); + } else { + tty_setinvert(tty); + } #endif + } tty_setnowrap(tty); for (size_t i = 0, p = 0; choice[i] != '\0'; i++) { @@ -218,9 +276,9 @@ static void draw(tty_interface_t *state) { const char *choice = choices_get(choices, i); if (choice) { int multi_sel = options->multi == 1 && is_selected((char *)choice); - tty_printf(tty, "%*s%c%c", options->pad, "", - i == choices->selection ? options->pointer : ' ', - multi_sel == 1 ? options->marker : ' '); + tty_printf(tty, "%*s%s%c%s%c%s", options->pad, "", + colors[POINTER_COLOR], i == choices->selection ? options->pointer : ' ', + colors[MARKER_COLOR], multi_sel == 1 ? options->marker : ' ', NC); draw_match(state, choice, i == choices->selection); } } @@ -229,7 +287,8 @@ static void draw(tty_interface_t *state) { tty_moveup(tty, num_lines + options->show_info); tty_setcol(tty, options->pad); - fputs(options->prompt, tty->fout); +// fputs(options->prompt, tty->fout); + fprintf(tty->fout, "%s%s%s", colors[PROMPT_COLOR], options->prompt, NC); for (size_t i = 0; i < state->cursor; i++) fputc(state->search[i], tty->fout); tty_flush(tty); @@ -330,7 +389,19 @@ static void action_next(tty_interface_t *state) { choices_next(state->choices); } +static void action_exit(tty_interface_t *state) { + clear(state); + tty_close(state->tty); + + state->exit = EXIT_FAILURE; +} + static void action_left(tty_interface_t *state) { + if (state->options->left_aborts == 1) { + action_exit(state); + return; + } + if (state->cursor > 0) { state->cursor--; while (!is_boundary(state->search[state->cursor]) && state->cursor) @@ -339,6 +410,11 @@ static void action_left(tty_interface_t *state) { } static void action_right(tty_interface_t *state) { + if (state->options->right_accepts == 1) { + action_emit(state); + return; + } + if (state->cursor < strlen(state->search)) { state->cursor++; while (!is_boundary(state->search[state->cursor])) @@ -366,13 +442,19 @@ static void action_pagedown(tty_interface_t *state) { choices_next(state->choices); } -static void action_autocomplete(tty_interface_t *state) { +static void action_tab(tty_interface_t *state) { if (state->options->multi == 1) { action_select(state); action_next(state); return; } + if (state->options->tab_accepts == 1) { + action_emit(state); + return; + } + + /* Autocomplete */ update_state(state); const char *current_selection = choices_get(state->choices, state->choices->selection); if (current_selection) { @@ -381,13 +463,6 @@ static void action_autocomplete(tty_interface_t *state) { } } -static void action_exit(tty_interface_t *state) { - clear(state); - tty_close(state->tty); - - state->exit = EXIT_FAILURE; -} - static void append_search(tty_interface_t *state, char ch) { char *search = state->search; size_t search_size = strlen(search); @@ -432,7 +507,7 @@ static const keybinding_t keybindings[] = {{"\x1b", action_exit}, /* ESC * {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 */ - {KEY_CTRL('I'), action_autocomplete}, /* TAB (C-I ) */ + {KEY_CTRL('I'), action_tab}, /* TAB (C-I ) */ {KEY_CTRL('C'), action_exit}, /* C-C */ {KEY_CTRL('D'), action_exit}, /* C-D */ {KEY_CTRL('G'), action_exit}, /* C-G */ @@ -509,6 +584,7 @@ static void handle_input(tty_interface_t *state, const char *s, int handle_ambig } int tty_interface_run(tty_interface_t *state) { + set_colors(); draw(state); for (;;) { From 797b6d908c35ca2e364153a69f3f0c024c096624 Mon Sep 17 00:00:00 2001 From: leo-arch Date: Mon, 18 Apr 2022 05:25:55 +0000 Subject: [PATCH 13/28] Update README.md --- README.md | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/README.md b/README.md index c3cb120..d053934 100644 --- a/README.md +++ b/README.md @@ -2,19 +2,6 @@ **fzy** is a fast, simple fuzzy text selector for the terminal with an advanced scoring algorithm. ---- - -**NOTE** - -Compared to `fzy` 1.0, this fork adds the following features: - -**1)** Left pad for the list of matches (`-P,--pad`) \ -**2)** Multi-selection (`-m,--multi`) \ -**3)** Pointer and marker customization (`--pointer` and `--marker` respectivelly) \ -**4)** Cyclic scrolling (`--cycle`). Defaults to disabled - ---- - [Try it out online!](http://jhawthorn.github.io/fzy-demo) ![](http://i.hawth.ca/u/fzy_animated_demo.svg) From 89cf4319f37a486889206f069d873d429cc807bd Mon Sep 17 00:00:00 2001 From: leo-arch Date: Mon, 18 Apr 2022 02:31:57 -0300 Subject: [PATCH 14/28] Add dash usage description --- fzy.1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fzy.1 b/fzy.1 index 23c645c..adaf130 100644 --- a/fzy.1 +++ b/fzy.1 @@ -131,7 +131,7 @@ Interface colors are read from the environment variable \fBFZY_COLORS\fR using a 6 = cyan 7 = white .sp -Use a \fBb\fR before the color to make it bold/bright. +Use a \fBb\fR before the color to make it bold/bright. A dash (-) means that the color for the interface element in that position must be skipped. .sp For example, \fBFZF_COLORS="-b1b2-4"\fR is to be read as follows: .sp From f32a0156071839946c05e9ea8d9342e8891e14a7 Mon Sep 17 00:00:00 2001 From: leo-arch Date: Mon, 18 Apr 2022 03:19:30 -0300 Subject: [PATCH 15/28] Add config.h header file --- .gitignore | 1 - config.h | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 config.h diff --git a/.gitignore b/.gitignore index 7db22fd..52a4942 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,4 @@ fzy fzytest *.o *.d -config.h test/acceptance/vendor/bundle diff --git a/config.h b/config.h new file mode 100644 index 0000000..a8c1f16 --- /dev/null +++ b/config.h @@ -0,0 +1,50 @@ +#define TTY_COLOR_HIGHLIGHT TTY_COLOR_YELLOW + +#define SCORE_GAP_LEADING -0.005 +#define SCORE_GAP_TRAILING -0.005 +#define SCORE_GAP_INNER -0.01 +#define SCORE_MATCH_CONSECUTIVE 1.0 +#define SCORE_MATCH_SLASH 0.9 +#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 + +#define DEFAULT_TTY "/dev/tty" +#define DEFAULT_PROMPT "> " +#define DEFAULT_NUM_LINES 10 +#define DEFAULT_WORKERS 0 +#define DEFAULT_SHOW_INFO 0 +#define DEFAULT_DELIMITER '\n' +#define DEFAULT_BENCHMARK 0 +#define DEFAULT_SCORES 0 +#define DEFAULT_SCROLLOFF 0 +#define DEFAULT_FILTER NULL +#define DEFAULT_INIT_SEARCH NULL +#define DEFAULT_MARKER '*' +#define DEFAULT_POINTER '>' +#define DEFAULT_PAD 0 +#define DEFAULT_MULTI 0 +#define DEFAULT_CYCLE 0 +#define DEFAULT_TAB_ACCEPTS 0 +#define DEFAULT_RIGHT_ACCEPTS 0 +#define DEFAULT_LEFT_ABORTS 0 + +#define DEF_PROMPT_COLOR "\x1b[0;36m" +#define DEF_POINTER_COLOR "\x1b[0;34m" +#define DEF_MARKER_COLOR "\x1b[0;32m" +#define DEF_SEL_FG_COLOR "\x1b[0m" +#define DEF_SEL_BG_COLOR "\x1b[7m" +#define NC "\x1b[0m" /* Reset attributes */ + +/* Color indices: colors (from FZY_COLORS env var) will be parsed + * exactly in this order. See tty_interface.c */ +#define PROMPT_COLOR 0 +#define POINTER_COLOR 1 +#define MARKER_COLOR 2 +#define SEL_FG_COLOR 3 +#define SEL_BG_COLOR 4 +#define COLOR_ITEMS_NUM 5 +#define MAX_COLOR_LEN 48 From 115abca0bcd470bf3ea2d732fd19238c251fb6d1 Mon Sep 17 00:00:00 2001 From: leo-arch Date: Mon, 18 Apr 2022 03:38:09 -0300 Subject: [PATCH 16/28] Fix compilation --- .gitignore | 1 + src/config.def.h | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/.gitignore b/.gitignore index 52a4942..7db22fd 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ fzy fzytest *.o *.d +config.h test/acceptance/vendor/bundle diff --git a/src/config.def.h b/src/config.def.h index fcdcc03..a8c1f16 100644 --- a/src/config.def.h +++ b/src/config.def.h @@ -17,3 +17,34 @@ #define DEFAULT_NUM_LINES 10 #define DEFAULT_WORKERS 0 #define DEFAULT_SHOW_INFO 0 +#define DEFAULT_DELIMITER '\n' +#define DEFAULT_BENCHMARK 0 +#define DEFAULT_SCORES 0 +#define DEFAULT_SCROLLOFF 0 +#define DEFAULT_FILTER NULL +#define DEFAULT_INIT_SEARCH NULL +#define DEFAULT_MARKER '*' +#define DEFAULT_POINTER '>' +#define DEFAULT_PAD 0 +#define DEFAULT_MULTI 0 +#define DEFAULT_CYCLE 0 +#define DEFAULT_TAB_ACCEPTS 0 +#define DEFAULT_RIGHT_ACCEPTS 0 +#define DEFAULT_LEFT_ABORTS 0 + +#define DEF_PROMPT_COLOR "\x1b[0;36m" +#define DEF_POINTER_COLOR "\x1b[0;34m" +#define DEF_MARKER_COLOR "\x1b[0;32m" +#define DEF_SEL_FG_COLOR "\x1b[0m" +#define DEF_SEL_BG_COLOR "\x1b[7m" +#define NC "\x1b[0m" /* Reset attributes */ + +/* Color indices: colors (from FZY_COLORS env var) will be parsed + * exactly in this order. See tty_interface.c */ +#define PROMPT_COLOR 0 +#define POINTER_COLOR 1 +#define MARKER_COLOR 2 +#define SEL_FG_COLOR 3 +#define SEL_BG_COLOR 4 +#define COLOR_ITEMS_NUM 5 +#define MAX_COLOR_LEN 48 From ef11838788281058a467f60dac3f433a6217d864 Mon Sep 17 00:00:00 2001 From: leo-arch Date: Mon, 18 Apr 2022 04:09:45 -0300 Subject: [PATCH 17/28] Run colored by default --- config.h | 7 ++----- src/config.def.h | 7 ++----- src/options.c | 8 +++++++- src/options.h | 1 + src/tty_interface.c | 5 +++-- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/config.h b/config.h index a8c1f16..6f8e36a 100644 --- a/config.h +++ b/config.h @@ -31,12 +31,9 @@ #define DEFAULT_TAB_ACCEPTS 0 #define DEFAULT_RIGHT_ACCEPTS 0 #define DEFAULT_LEFT_ABORTS 0 +#define DEFAULT_NO_COLOR 0 -#define DEF_PROMPT_COLOR "\x1b[0;36m" -#define DEF_POINTER_COLOR "\x1b[0;34m" -#define DEF_MARKER_COLOR "\x1b[0;32m" -#define DEF_SEL_FG_COLOR "\x1b[0m" -#define DEF_SEL_BG_COLOR "\x1b[7m" +#define DEFAULT_COLORS "b6b1b2b40" #define NC "\x1b[0m" /* Reset attributes */ /* Color indices: colors (from FZY_COLORS env var) will be parsed diff --git a/src/config.def.h b/src/config.def.h index a8c1f16..6f8e36a 100644 --- a/src/config.def.h +++ b/src/config.def.h @@ -31,12 +31,9 @@ #define DEFAULT_TAB_ACCEPTS 0 #define DEFAULT_RIGHT_ACCEPTS 0 #define DEFAULT_LEFT_ABORTS 0 +#define DEFAULT_NO_COLOR 0 -#define DEF_PROMPT_COLOR "\x1b[0;36m" -#define DEF_POINTER_COLOR "\x1b[0;34m" -#define DEF_MARKER_COLOR "\x1b[0;32m" -#define DEF_SEL_FG_COLOR "\x1b[0m" -#define DEF_SEL_BG_COLOR "\x1b[7m" +#define DEFAULT_COLORS "b6b1b2b40" #define NC "\x1b[0m" /* Reset attributes */ /* Color indices: colors (from FZY_COLORS env var) will be parsed diff --git a/src/options.c b/src/options.c index 9db07ce..539d6fd 100644 --- a/src/options.c +++ b/src/options.c @@ -29,7 +29,8 @@ static const char *usage_str = " --cycle Enable cyclic scrolling\n" " --tab-accepts TAB accepts\n" " --right-accepts Right arrow key accepts\n" - " --left-aborts Left arrow key aborts\n"; + " --left-aborts Left arrow key aborts\n" + " --no-color Run colorless\n"; static void usage(const char *argv0) { fprintf(stderr, usage_str, argv0); @@ -56,6 +57,7 @@ static struct option longopts[] = { {"tab-accepts", no_argument, NULL, 4}, {"right-accepts", no_argument, NULL, 5}, {"left-aborts", no_argument, NULL, 6}, + {"no-color", no_argument, NULL, 7}, {NULL, 0, NULL, 0} }; @@ -80,6 +82,7 @@ void options_init(options_t *options) { options->tab_accepts = DEFAULT_TAB_ACCEPTS; options->right_accepts = DEFAULT_RIGHT_ACCEPTS; options->left_aborts = DEFAULT_LEFT_ABORTS; + options->no_color = DEFAULT_NO_COLOR; } void options_parse(options_t *options, int argc, char *argv[]) { @@ -169,6 +172,9 @@ void options_parse(options_t *options, int argc, char *argv[]) { case 6: options->left_aborts = 1; break; + case 7: + options->no_color = 1; + break; case 'h': /* fallthrough */ default: diff --git a/src/options.h b/src/options.h index 0297dc1..a7dc2f0 100644 --- a/src/options.h +++ b/src/options.h @@ -21,6 +21,7 @@ typedef struct { int tab_accepts; int right_accepts; int left_aborts; + int no_color; } options_t; void options_init(options_t *options); diff --git a/src/tty_interface.c b/src/tty_interface.c index 99b3441..0b01bad 100644 --- a/src/tty_interface.c +++ b/src/tty_interface.c @@ -39,7 +39,7 @@ set_colors(void) p = getenv("FZY_COLORS"); if (!p || !*p) - return; + p = DEFAULT_COLORS; size_t i, b = 0, c = 0; for (i = 0; p[i] && c < COLOR_ITEMS_NUM; i++) { @@ -584,7 +584,8 @@ static void handle_input(tty_interface_t *state, const char *s, int handle_ambig } int tty_interface_run(tty_interface_t *state) { - set_colors(); + if (state->options->no_color == 0) + set_colors(); draw(state); for (;;) { From eab6fb9d66415c3bc0cc2c0fac0df79118f5e541 Mon Sep 17 00:00:00 2001 From: leo-arch Date: Mon, 18 Apr 2022 04:23:14 -0300 Subject: [PATCH 18/28] Update manpage --- fzy.1 | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fzy.1 b/fzy.1 index adaf130..e320bd1 100644 --- a/fzy.1 +++ b/fzy.1 @@ -84,6 +84,10 @@ Right arrow key accepts: print selection and exit .BR \-\-left-aborts Left arrow key aborts: cancel selection and exit . +.TP +.BR \-\-no\-color +Run colorless +. .SH KEYS . .TP @@ -140,6 +144,8 @@ For example, \fBFZF_COLORS="-b1b2-4"\fR is to be read as follows: \fBb2\fR: bold green marker color \fB-\fR: no color for the current entry foreground \fB4\fR: blue current entry background +.sp +Default colors are: \fBb6b1b2b40\fR . .SH USAGE EXAMPLES . From d1f116f3601ec9692e1ecf345be0f2b993f92944 Mon Sep 17 00:00:00 2001 From: leo-arch Date: Wed, 20 Apr 2022 17:52:25 -0300 Subject: [PATCH 19/28] Handle ANSI colors for matches --- src/choices.c | 4 +- src/tty_interface.c | 149 +++++++++++++++++++++++++++++++------------- 2 files changed, 108 insertions(+), 45 deletions(-) diff --git a/src/choices.c b/src/choices.c index 8b04267..f185cfc 100644 --- a/src/choices.c +++ b/src/choices.c @@ -258,7 +258,7 @@ static void *choices_search_worker(void *data) { w->result = merge2(w->result, job->workers[next_worker].result); } - return NULL; + return (char *)NULL; } void choices_search(choices_t *c, const char *search) { @@ -314,7 +314,7 @@ const char *choices_get(choices_t *c, size_t n) { if (n < c->available) { return c->results[n].str; } else { - return NULL; + return (char *)NULL; } } diff --git a/src/tty_interface.c b/src/tty_interface.c index 0b01bad..680550c 100644 --- a/src/tty_interface.c +++ b/src/tty_interface.c @@ -7,8 +7,20 @@ #include "tty_interface.h" #include "../config.h" +#ifndef PATH_MAX +# ifdef __linux__ +# define PATH_MAX 4096 +# else +# define PATH_MAX 1024 +# endif /* __linux */ +#endif /* PATH_MAX */ + +#define _ESC 27 + /* Array to store selected/marked entries */ static char **selections = (char **)NULL; +/* A buffer big enough to hold decolored entries */ +static char buf[PATH_MAX]; /* SEL_N is the current size of the selections array, while SEL_COUNTER * is the current amount of actually selected entries */ @@ -68,15 +80,13 @@ set_colors(void) static int is_selected(const char *p) { - if (!p || !*p || sel_counter == 0) { + if (!p || !*p || sel_counter == 0) return 0; - } size_t i; for (i = 0; selections[i]; i++) { - if (*selections[i] == *p && strcmp(selections[i], p) == 0) { + if (*selections[i] == *p && strcmp(selections[i], p) == 0) return 1; - } } return 0; @@ -87,21 +97,48 @@ is_selected(const char *p) static void deselect_entry(char *name) { - if (!name || !*name || sel_counter == 0) { + if (!name || !*name || sel_counter == 0) return; - } size_t i; for (i = 0; selections[i]; i++) { - if (*selections[i] != *name || strcmp(selections[i], name) != 0) { + if (*selections[i] != *name || strcmp(selections[i], name) != 0) continue; - } *selections[i] = '\0'; sel_counter--; break; } } +static char * +decolor_name(const char *name) +{ + if (!name) + return (char *)NULL; + + char *p = buf, *q = buf; + + size_t i, j = 0; + for (i = 0; name[i]; i++) { + if (name[i] == _ESC && name[i + 1] == '[') { + for (j = i + 1; name[j]; j++) { + if (name[j] != 'm') + continue; + i = j + (name[j + 1] == _ESC ? 0 : 1); + break; + } + } + + if (i == j) /* We have another escape code */ + continue; + *p = name[i]; + p++; + } + + *p = '\0'; + return *q ? q : (char *)NULL; +} + /* Save the string P into the selections array */ static void save_selection(const char *p) @@ -120,9 +157,8 @@ static int action_select(tty_interface_t *state) { const char *p = choices_get(state->choices, state->choices->selection); - if (!p) { + if (!p) return EXIT_FAILURE; - } if (is_selected(p) == 1) { deselect_entry((char *)p); @@ -130,7 +166,6 @@ action_select(tty_interface_t *state) } save_selection(p); - return EXIT_SUCCESS; } @@ -138,16 +173,17 @@ action_select(tty_interface_t *state) static void print_selections(tty_interface_t *state) { - if (sel_counter == 0 || state->options->multi == 0) { + if (sel_counter == 0 || state->options->multi == 0) return; - } size_t i; for (i = 0; selections[i]; i++) { - if (!*selections[i]) { + if (!*selections[i]) continue; - } - printf("%s\n", selections[i]); + char *p = (char *)NULL; + if (strchr(selections[i], 27)) + p = decolor_name(selections[i]); + printf("%s\n", p ? p : selections[i]); } } @@ -156,14 +192,12 @@ print_selections(tty_interface_t *state) static void free_selections(tty_interface_t *state) { - if (state->options->multi == 0 || seln == 0 || !selections) { + if (state->options->multi == 0 || seln == 0 || !selections) return; - } size_t i; - for (i = 0; selections[i]; i++) { + for (i = 0; selections[i]; i++) free(selections[i]); - } free(selections); selections = (char **)NULL; } @@ -261,6 +295,10 @@ static void draw(tty_interface_t *state) { } } + int invert = 0; + if (invert == 1) + fprintf(tty->fout, "\x1b[%dB", num_lines + 1); + tty_setcol(tty, options->pad); tty_printf(tty, "%s%s", options->prompt, state->search); tty_clearline(tty); @@ -270,6 +308,9 @@ static void draw(tty_interface_t *state) { tty_clearline(tty); } + if (invert == 1) + fprintf(tty->fout, "\x1b[%dA", num_lines + 1); + for (size_t i = start; i < start + num_lines; i++) { tty_printf(tty, "\n"); tty_clearline(tty); @@ -277,17 +318,21 @@ static void draw(tty_interface_t *state) { if (choice) { int multi_sel = options->multi == 1 && is_selected((char *)choice); tty_printf(tty, "%*s%s%c%s%c%s", options->pad, "", - colors[POINTER_COLOR], i == choices->selection ? options->pointer : ' ', - colors[MARKER_COLOR], multi_sel == 1 ? options->marker : ' ', NC); + colors[POINTER_COLOR], i == choices->selection + ? options->pointer : ' ', + colors[MARKER_COLOR], multi_sel == 1 ? + options->marker : ' ', NC); draw_match(state, choice, i == choices->selection); } } - if (num_lines + options->show_info) + if (invert == 0 && num_lines + options->show_info) tty_moveup(tty, num_lines + options->show_info); + if (invert == 1) + fprintf(tty->fout, "%c", '\n'); + tty_setcol(tty, options->pad); -// fputs(options->prompt, tty->fout); fprintf(tty->fout, "%s%s%s", colors[PROMPT_COLOR], options->prompt, NC); for (size_t i = 0; i < state->cursor; i++) fputc(state->search[i], tty->fout); @@ -309,7 +354,7 @@ static void update_state(tty_interface_t *state) { static void action_emit(tty_interface_t *state) { update_state(state); - if (state->options->multi == 1) { + if (state->options->multi == 1 && seln > 0) { clear(state); tty_close(state->tty); @@ -328,7 +373,10 @@ static void action_emit(tty_interface_t *state) { const char *selection = choices_get(state->choices, state->choices->selection); if (selection) { /* output the selected result */ - printf("%s\n", selection); + char *p = (char *)NULL; + if (strchr(selection, 27)) + p = decolor_name(selection); + printf("%s\n", p ? p : selection); } else { /* No match, output the query instead */ printf("%s\n", state->search); @@ -348,7 +396,8 @@ static void action_del_char(tty_interface_t *state) { state->cursor--; } while (!is_boundary(state->search[state->cursor]) && state->cursor); - memmove(&state->search[state->cursor], &state->search[original_cursor], length - original_cursor + 1); + memmove(&state->search[state->cursor], &state->search[original_cursor], + length - original_cursor + 1); } static void action_del_word(tty_interface_t *state) { @@ -361,12 +410,14 @@ static void action_del_word(tty_interface_t *state) { while (cursor && !isspace(state->search[cursor - 1])) cursor--; - memmove(&state->search[cursor], &state->search[original_cursor], strlen(state->search) - original_cursor + 1); + 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) { - memmove(state->search, &state->search[state->cursor], strlen(state->search) - state->cursor + 1); + memmove(state->search, &state->search[state->cursor], + strlen(state->search) - state->cursor + 1); state->cursor = 0; } @@ -432,13 +483,15 @@ static void action_end(tty_interface_t *state) { 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); } @@ -456,9 +509,11 @@ static void action_tab(tty_interface_t *state) { /* Autocomplete */ update_state(state); - const char *current_selection = choices_get(state->choices, state->choices->selection); + 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); + strncpy(state->search, choices_get(state->choices, + state->choices->selection), SEARCH_SIZE_MAX); state->cursor = strlen(state->search); } } @@ -467,14 +522,16 @@ static void append_search(tty_interface_t *state, char ch) { char *search = state->search; size_t search_size = strlen(search); if (search_size < SEARCH_SIZE_MAX) { - memmove(&search[state->cursor+1], &search[state->cursor], search_size - state->cursor + 1); + memmove(&search[state->cursor+1], &search[state->cursor], + search_size - state->cursor + 1); search[state->cursor] = ch; state->cursor++; } } -void tty_interface_init(tty_interface_t *state, tty_t *tty, choices_t *choices, options_t *options) { +void tty_interface_init(tty_interface_t *state, tty_t *tty, +choices_t *choices, options_t *options) { state->tty = tty; state->choices = choices; state->options = options; @@ -501,9 +558,9 @@ typedef struct { #define KEY_CTRL(key) ((const char[]){((key) - ('@')), '\0'}) -static const keybinding_t keybindings[] = {{"\x1b", action_exit}, /* ESC */ - {"\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 */ @@ -574,10 +631,15 @@ static void handle_input(tty_interface_t *state, const char *s, int handle_ambig if (in_middle) return; - /* No matching keybinding, add to search */ - for (int i = 0; input[i]; i++) - if (isprint_unicode(input[i])) - append_search(state, input[i]); + /* No matching keybinding, decolorize and add to search */ + char *p = input, *q = (char *)NULL; + if (strchr(input, _ESC) && (q = decolor_name(input))) + p = q; + + for (int i = 0; p[i]; i++) { + if (isprint_unicode(p[i])) + append_search(state, p[i]); + } /* We have processed the input, so clear it */ strcpy(input, ""); @@ -604,7 +666,8 @@ int tty_interface_run(tty_interface_t *state) { } draw(state); - } while (tty_input_ready(state->tty, state->ambiguous_key_pending ? KEYTIMEOUT : 0, 0)); + } while (tty_input_ready(state->tty, + state->ambiguous_key_pending ? KEYTIMEOUT : 0, 0)); if (state->ambiguous_key_pending) { char s[1] = ""; From 76dc15658f61bf7e69f0d6aa528a41bd6760c5a1 Mon Sep 17 00:00:00 2001 From: leo-arch Date: Thu, 5 May 2022 03:18:22 -0300 Subject: [PATCH 20/28] Add prompt at bottom option (reverse) --- config.h | 3 + src/choices.c | 99 +++++++++++------- src/config.def.h | 3 + src/fzy.c | 4 +- src/match.c | 32 ++++-- src/options.c | 162 +++++++++++++---------------- src/options.h | 1 + src/tty.c | 94 ++++++++++++----- src/tty_interface.c | 244 +++++++++++++++++++++++++++++++++----------- 9 files changed, 424 insertions(+), 218 deletions(-) diff --git a/config.h b/config.h index 6f8e36a..f6bb662 100644 --- a/config.h +++ b/config.h @@ -1,5 +1,7 @@ #define TTY_COLOR_HIGHLIGHT TTY_COLOR_YELLOW +#define VERSION "1.0" + #define SCORE_GAP_LEADING -0.005 #define SCORE_GAP_TRAILING -0.005 #define SCORE_GAP_INNER -0.01 @@ -32,6 +34,7 @@ #define DEFAULT_RIGHT_ACCEPTS 0 #define DEFAULT_LEFT_ABORTS 0 #define DEFAULT_NO_COLOR 0 +#define DEFAULT_REVERSE 0 #define DEFAULT_COLORS "b6b1b2b40" #define NC "\x1b[0m" /* Reset attributes */ diff --git a/src/choices.c b/src/choices.c index f185cfc..6f1b012 100644 --- a/src/choices.c +++ b/src/choices.c @@ -15,7 +15,9 @@ /* Initial size of choices array */ #define INITIAL_CHOICE_CAPACITY 128 -static int cmpchoice(const void *_idx1, const void *_idx2) { +static int +cmpchoice(const void *_idx1, const void *_idx2) +{ const struct scored_result *a = _idx1; const struct scored_result *b = _idx2; @@ -36,7 +38,9 @@ static int cmpchoice(const void *_idx1, const void *_idx2) { } } -static void *safe_realloc(void *buffer, size_t size) { +static void * +safe_realloc(void *buffer, size_t size) +{ buffer = realloc(buffer, size); if (!buffer) { fprintf(stderr, "Error: Can't allocate memory (%zu bytes)\n", size); @@ -46,7 +50,9 @@ static void *safe_realloc(void *buffer, size_t size) { return buffer; } -void choices_fread(choices_t *c, FILE *file, char input_delimiter) { +void +choices_fread(choices_t *c, FILE *file, char input_delimiter) +{ /* Save current position for parsing later */ size_t buffer_start = c->buffer_size; @@ -87,18 +93,24 @@ void choices_fread(choices_t *c, FILE *file, char input_delimiter) { } while (line && line < line_end); } -static void choices_resize(choices_t *c, size_t new_capacity) { +static void +choices_resize(choices_t *c, size_t new_capacity) +{ c->strings = safe_realloc(c->strings, new_capacity * sizeof(const char *)); c->capacity = new_capacity; } -static void choices_reset_search(choices_t *c) { +static void +choices_reset_search(choices_t *c) +{ free(c->results); c->selection = c->available = 0; c->results = NULL; } -void choices_init(choices_t *c, options_t *options) { +void +choices_init(choices_t *c, options_t *options) +{ c->strings = NULL; c->results = NULL; @@ -108,16 +120,17 @@ void choices_init(choices_t *c, options_t *options) { c->capacity = c->size = 0; choices_resize(c, INITIAL_CHOICE_CAPACITY); - if (options->workers) { + if (options->workers) c->worker_count = options->workers; - } else { + else c->worker_count = (int)sysconf(_SC_NPROCESSORS_ONLN); - } choices_reset_search(c); } -void choices_destroy(choices_t *c) { +void +choices_destroy(choices_t *c) +{ free(c->buffer); c->buffer = NULL; c->buffer_size = 0; @@ -131,17 +144,21 @@ void choices_destroy(choices_t *c) { c->available = c->selection = 0; } -void choices_add(choices_t *c, const char *choice) { +void +choices_add(choices_t *c, const char *choice) +{ /* Previous search is now invalid */ choices_reset_search(c); - if (c->size == c->capacity) { + if (c->size == c->capacity) choices_resize(c, c->capacity * 2); - } + c->strings[c->size++] = choice; } -size_t choices_available(choices_t *c) { +size_t +choices_available(choices_t *c) +{ return c->available; } @@ -167,22 +184,24 @@ struct worker { struct result_list result; }; -static void worker_get_next_batch(struct search_job *job, size_t *start, size_t *end) { +static void +worker_get_next_batch(struct search_job *job, size_t *start, size_t *end) +{ pthread_mutex_lock(&job->lock); *start = job->processed; job->processed += BATCH_SIZE; - if (job->processed > job->choices->size) { + if (job->processed > job->choices->size) job->processed = job->choices->size; - } *end = job->processed; pthread_mutex_unlock(&job->lock); } -static struct result_list merge2(struct result_list list1, struct result_list list2) { +static struct result_list merge2(struct result_list list1, struct result_list list2) +{ size_t result_index = 0, index1 = 0, index2 = 0; struct result_list result; @@ -194,19 +213,17 @@ static struct result_list merge2(struct result_list list1, struct result_list li } while(index1 < list1.size && index2 < list2.size) { - if (cmpchoice(&list1.list[index1], &list2.list[index2]) < 0) { + if (cmpchoice(&list1.list[index1], &list2.list[index2]) < 0) result.list[result_index++] = list1.list[index1++]; - } else { + else result.list[result_index++] = list2.list[index2++]; - } } - while(index1 < list1.size) { + while(index1 < list1.size) result.list[result_index++] = list1.list[index1++]; - } - while(index2 < list2.size) { + + while(index2 < list2.size) result.list[result_index++] = list2.list[index2++]; - } free(list1.list); free(list2.list); @@ -214,7 +231,9 @@ static struct result_list merge2(struct result_list list1, struct result_list li return result; } -static void *choices_search_worker(void *data) { +static void * +choices_search_worker(void *data) +{ struct worker *w = (struct worker *)data; struct search_job *job = w->job; const choices_t *c = job->choices; @@ -225,9 +244,8 @@ static void *choices_search_worker(void *data) { for(;;) { worker_get_next_batch(job, &start, &end); - if(start == end) { + if(start == end) break; - } for(size_t i = start; i < end; i++) { if (has_match(job->search, c->strings[i])) { @@ -261,7 +279,9 @@ static void *choices_search_worker(void *data) { return (char *)NULL; } -void choices_search(choices_t *c, const char *search) { +void +choices_search(choices_t *c, const char *search) +{ choices_reset_search(c); struct search_job *job = calloc(1, sizeof(struct search_job)); @@ -310,24 +330,31 @@ void choices_search(choices_t *c, const char *search) { free(job); } -const char *choices_get(choices_t *c, size_t n) { - if (n < c->available) { +const char * +choices_get(choices_t *c, size_t n) +{ + if (n < c->available) return c->results[n].str; - } else { + else return (char *)NULL; - } } -score_t choices_getscore(choices_t *c, size_t n) { +score_t +choices_getscore(choices_t *c, size_t n) +{ return c->results[n].score; } -void choices_prev(choices_t *c) { +void +choices_prev(choices_t *c) +{ if (c->available) c->selection = (c->selection + c->available - 1) % c->available; } -void choices_next(choices_t *c) { +void +choices_next(choices_t *c) +{ if (c->available) c->selection = (c->selection + 1) % c->available; } diff --git a/src/config.def.h b/src/config.def.h index 6f8e36a..393a148 100644 --- a/src/config.def.h +++ b/src/config.def.h @@ -32,6 +32,7 @@ #define DEFAULT_RIGHT_ACCEPTS 0 #define DEFAULT_LEFT_ABORTS 0 #define DEFAULT_NO_COLOR 0 +#define DEFAULT_REVERSE 0 #define DEFAULT_COLORS "b6b1b2b40" #define NC "\x1b[0m" /* Reset attributes */ @@ -45,3 +46,5 @@ #define SEL_BG_COLOR 4 #define COLOR_ITEMS_NUM 5 #define MAX_COLOR_LEN 48 + +#define VERSION 1.0 diff --git a/src/fzy.c b/src/fzy.c index 967a1fc..2a0d4fe 100644 --- a/src/fzy.c +++ b/src/fzy.c @@ -13,7 +13,9 @@ #include "../config.h" -int main(int argc, char *argv[]) { +int +main(int argc, char *argv[]) +{ int ret = 0; options_t options; diff --git a/src/match.c b/src/match.c index d618f0a..0709bb9 100644 --- a/src/match.c +++ b/src/match.c @@ -11,12 +11,16 @@ #include "../config.h" -char *strcasechr(const char *s, char c) { +char * +strcasechr(const char *s, char c) +{ const char accept[3] = {c, toupper(c), 0}; return strpbrk(s, accept); } -int has_match(const char *needle, const char *haystack) { +int +has_match(const char *needle, const char *haystack) +{ while (*needle) { char nch = *needle++; @@ -42,7 +46,9 @@ struct match_struct { score_t match_bonus[MATCH_MAX_LEN]; }; -static void precompute_bonus(const char *haystack, score_t *match_bonus) { +static void +precompute_bonus(const char *haystack, score_t *match_bonus) +{ /* Which positions are beginning of words */ char last_ch = '/'; for (int i = 0; haystack[i]; i++) { @@ -52,13 +58,14 @@ static void precompute_bonus(const char *haystack, score_t *match_bonus) { } } -static void setup_match_struct(struct match_struct *match, const char *needle, const char *haystack) { +static void +setup_match_struct(struct match_struct *match, const char *needle, const char *haystack) +{ match->needle_len = strlen(needle); match->haystack_len = strlen(haystack); - if (match->haystack_len > MATCH_MAX_LEN || match->needle_len > match->haystack_len) { + if (match->haystack_len > MATCH_MAX_LEN || match->needle_len > match->haystack_len) return; - } for (int i = 0; i < match->needle_len; i++) match->lower_needle[i] = tolower(needle[i]); @@ -69,7 +76,10 @@ static void setup_match_struct(struct match_struct *match, const char *needle, c precompute_bonus(haystack, match->match_bonus); } -static inline void match_row(const struct match_struct *match, int row, score_t *curr_D, score_t *curr_M, const score_t *last_D, const score_t *last_M) { +static inline void +match_row(const struct match_struct *match, int row, score_t *curr_D, score_t *curr_M, +const score_t *last_D, const score_t *last_M) +{ int n = match->needle_len; int m = match->haystack_len; int i = row; @@ -102,7 +112,9 @@ static inline void match_row(const struct match_struct *match, int row, score_t } } -score_t match(const char *needle, const char *haystack) { +score_t +match(const char *needle, const char *haystack) +{ if (!*needle) return SCORE_MIN; @@ -151,7 +163,9 @@ score_t match(const char *needle, const char *haystack) { return last_M[m - 1]; } -score_t match_positions(const char *needle, const char *haystack, size_t *positions) { +score_t +match_positions(const char *needle, const char *haystack, size_t *positions) +{ if (!*needle) return SCORE_MIN; diff --git a/src/options.c b/src/options.c index 539d6fd..9858bcf 100644 --- a/src/options.c +++ b/src/options.c @@ -30,9 +30,12 @@ static const char *usage_str = " --tab-accepts TAB accepts\n" " --right-accepts Right arrow key accepts\n" " --left-aborts Left arrow key aborts\n" + " --reverse Display from top, prompt at bottom\n" " --no-color Run colorless\n"; -static void usage(const char *argv0) { +static void +usage(const char *argv0) +{ fprintf(stderr, usage_str, argv0); } @@ -58,11 +61,14 @@ static struct option longopts[] = { {"right-accepts", no_argument, NULL, 5}, {"left-aborts", no_argument, NULL, 6}, {"no-color", no_argument, NULL, 7}, + {"reverse", no_argument, NULL, 8}, {NULL, 0, NULL, 0} }; -void options_init(options_t *options) { - /* set defaults */ +void +options_init(options_t *options) +{ + /* Set defaults */ options->benchmark = DEFAULT_BENCHMARK; options->filter = DEFAULT_FILTER; options->init_search = DEFAULT_INIT_SEARCH; @@ -83,103 +89,81 @@ void options_init(options_t *options) { options->right_accepts = DEFAULT_RIGHT_ACCEPTS; options->left_aborts = DEFAULT_LEFT_ABORTS; options->no_color = DEFAULT_NO_COLOR; + options->reverse = DEFAULT_REVERSE; } -void options_parse(options_t *options, int argc, char *argv[]) { +void +options_parse(options_t *options, int argc, char *argv[]) +{ options_init(options); int c; while ((c = getopt_long(argc, argv, "mvhs0e:q:l:t:p:P:j:i", longopts, NULL)) != -1) { switch (c) { - case 'v': - printf("%s " VERSION " © 2014-2018 John Hawthorn\n", argv[0]); - exit(EXIT_SUCCESS); - case 's': - options->show_scores = 1; - break; - case '0': - options->input_delimiter = '\0'; - break; - case 'm': - options->multi = 1; - break; - case 'q': - options->init_search = optarg; - break; - case 'e': - options->filter = optarg; - break; - case 'b': - if (optarg) { - if (sscanf(optarg, "%d", &options->benchmark) != 1) { - usage(argv[0]); - exit(EXIT_FAILURE); - } - } else { - options->benchmark = 100; - } - break; - case 't': - options->tty_filename = optarg; - break; - case 'p': - options->prompt = optarg; - break; - case 'P': - if (optarg && *optarg && *optarg >= '0' && *optarg <= '9') - options->pad = atoi(optarg); - break; - case 'j': - if (sscanf(optarg, "%u", &options->workers) != 1) { + case 'v': + printf("%s %s © 2014-2018 John Hawthorn\n", argv[0], VERSION); + exit(EXIT_SUCCESS); + case 's': options->show_scores = 1; break; + case '0': options->input_delimiter = '\0'; break; + case 'm': options->multi = 1; break; + case 'q': options->init_search = optarg; break; + case 'e': options->filter = optarg; break; + case 'b': + if (optarg) { + if (sscanf(optarg, "%d", &options->benchmark) != 1) { usage(argv[0]); exit(EXIT_FAILURE); } - break; - case 'l': { - if (!optarg) - break; - int l; - if (!strcmp(optarg, "max")) { - l = INT_MAX; - } else if (sscanf(optarg, "%d", &l) != 1 || l < 3) { - fprintf(stderr, "Invalid format for --lines: %s\n", optarg); - fprintf(stderr, "Must be integer in range 3..\n"); - usage(argv[0]); - exit(EXIT_FAILURE); - } - options->num_lines = l; - } break; - case 'i': - options->show_info = 1; - break; - case 1: - if (optarg && *optarg) - options->pointer = *optarg; - break; - case 2: - if (optarg && *optarg) - options->marker = *optarg; - break; - case 3: - options->cycle = 1; - break; - case 4: - options->tab_accepts = 1; - break; - case 5: - options->right_accepts = 1; - break; - case 6: - options->left_aborts = 1; - break; - case 7: - options->no_color = 1; - break; - - case 'h': /* fallthrough */ - default: + } else { + options->benchmark = 100; + } + break; + case 't': options->tty_filename = optarg; break; + case 'p': options->prompt = optarg; break; + case 'P': + if (optarg && *optarg && *optarg >= '0' && *optarg <= '9') + options->pad = atoi(optarg); + break; + case 'j': + if (sscanf(optarg, "%u", &options->workers) != 1) { + usage(argv[0]); + exit(EXIT_FAILURE); + } + break; + case 'l': { + if (!optarg) + break; + int l; + if (!strcmp(optarg, "max")) { + l = INT_MAX; + } else if (sscanf(optarg, "%d", &l) != 1 || l < 3) { + fprintf(stderr, "Invalid format for --lines: %s\n", optarg); + fprintf(stderr, "Must be integer in range 3..\n"); usage(argv[0]); - exit(EXIT_SUCCESS); + exit(EXIT_FAILURE); + } + options->num_lines = l; + } break; + case 'i': options->show_info = 1; break; + case 1: + if (optarg && *optarg) + options->pointer = *optarg; + break; + case 2: + if (optarg && *optarg) + options->marker = *optarg; + break; + case 3: options->cycle = 1; break; + case 4: options->tab_accepts = 1; break; + case 5: options->right_accepts = 1; break; + case 6: options->left_aborts = 1; break; + case 7: options->no_color = 1; break; + case 8: options->reverse = 1; break; + + case 'h': /* fallthrough */ + default: + usage(argv[0]); + exit(EXIT_SUCCESS); } } if (optind != argc) { diff --git a/src/options.h b/src/options.h index a7dc2f0..10799c6 100644 --- a/src/options.h +++ b/src/options.h @@ -22,6 +22,7 @@ typedef struct { int right_accepts; int left_aborts; int no_color; + int reverse; } options_t; void options_init(options_t *options); diff --git a/src/tty.c b/src/tty.c index 733477e..7450ffe 100644 --- a/src/tty.c +++ b/src/tty.c @@ -13,21 +13,29 @@ #include "../config.h" -void tty_reset(tty_t *tty) { +void +tty_reset(tty_t *tty) +{ tcsetattr(tty->fdin, TCSANOW, &tty->original_termios); } -void tty_close(tty_t *tty) { +void +tty_close(tty_t *tty) +{ tty_reset(tty); fclose(tty->fout); close(tty->fdin); } -static void handle_sigwinch(int sig){ +static void +handle_sigwinch(int sig) +{ (void)sig; } -void tty_init(tty_t *tty, const char *tty_filename) { +void +tty_init(tty_t *tty, const char *tty_filename) +{ tty->fdin = open(tty_filename, O_RDONLY); if (tty->fdin < 0) { perror("Failed to open tty"); @@ -66,13 +74,13 @@ void tty_init(tty_t *tty, const char *tty_filename) { perror("tcsetattr"); tty_getwinsz(tty); - tty_setnormal(tty); - signal(SIGWINCH, handle_sigwinch); } -void tty_getwinsz(tty_t *tty) { +void +tty_getwinsz(tty_t *tty) +{ struct winsize ws; if (ioctl(fileno(tty->fout), TIOCGWINSZ, &ws) == -1) { tty->maxwidth = 80; @@ -83,7 +91,9 @@ void tty_getwinsz(tty_t *tty) { } } -char tty_getchar(tty_t *tty) { +char +tty_getchar(tty_t *tty) +{ char ch; int size = read(tty->fdin, &ch, 1); if (size < 0) { @@ -97,7 +107,9 @@ char tty_getchar(tty_t *tty) { } } -int tty_input_ready(tty_t *tty, long int timeout, int return_on_signal) { +int +tty_input_ready(tty_t *tty, long int timeout, int return_on_signal) +{ fd_set readfs; FD_ZERO(&readfs); FD_SET(tty->fdin, &readfs); @@ -129,73 +141,105 @@ int tty_input_ready(tty_t *tty, long int timeout, int return_on_signal) { } } -static void tty_sgr(tty_t *tty, int code) { +static void +tty_sgr(tty_t *tty, int code) +{ tty_printf(tty, "%c%c%im", 0x1b, '[', code); } -void tty_setfg(tty_t *tty, int fg) { +void +tty_setfg(tty_t *tty, int fg) +{ if (tty->fgcolor != fg) { tty_sgr(tty, 30 + fg); tty->fgcolor = fg; } } -void tty_setinvert(tty_t *tty) { +void +tty_setinvert(tty_t *tty) +{ tty_sgr(tty, 7); } -void tty_setunderline(tty_t *tty) { +void +tty_setunderline(tty_t *tty) +{ tty_sgr(tty, 4); } -void tty_setnormal(tty_t *tty) { +void +tty_setnormal(tty_t *tty) +{ tty_sgr(tty, 0); tty->fgcolor = 9; } -void tty_setnowrap(tty_t *tty) { +void +tty_setnowrap(tty_t *tty) +{ tty_printf(tty, "%c%c?7l", 0x1b, '['); } -void tty_setwrap(tty_t *tty) { +void +tty_setwrap(tty_t *tty) +{ tty_printf(tty, "%c%c?7h", 0x1b, '['); } -void tty_newline(tty_t *tty) { +void +tty_newline(tty_t *tty) +{ tty_printf(tty, "%c%cK\n", 0x1b, '['); } -void tty_clearline(tty_t *tty) { +void +tty_clearline(tty_t *tty) +{ tty_printf(tty, "%c%cK", 0x1b, '['); } -void tty_setcol(tty_t *tty, int col) { +void +tty_setcol(tty_t *tty, int col) +{ tty_printf(tty, "%c%c%iG", 0x1b, '[', col + 1); } -void tty_moveup(tty_t *tty, int i) { +void +tty_moveup(tty_t *tty, int i) +{ tty_printf(tty, "%c%c%iA", 0x1b, '[', i); } -void tty_printf(tty_t *tty, const char *fmt, ...) { +void +tty_printf(tty_t *tty, const char *fmt, ...) +{ va_list args; va_start(args, fmt); vfprintf(tty->fout, fmt, args); va_end(args); } -void tty_putc(tty_t *tty, char c) { +void +tty_putc(tty_t *tty, char c) +{ fputc(c, tty->fout); } -void tty_flush(tty_t *tty) { +void +tty_flush(tty_t *tty) +{ fflush(tty->fout); } -size_t tty_getwidth(tty_t *tty) { +size_t +tty_getwidth(tty_t *tty) +{ return tty->maxwidth; } -size_t tty_getheight(tty_t *tty) { +size_t +tty_getheight(tty_t *tty) +{ return tty->maxheight; } diff --git a/src/tty_interface.c b/src/tty_interface.c index 680550c..2cfb121 100644 --- a/src/tty_interface.c +++ b/src/tty_interface.c @@ -181,7 +181,7 @@ print_selections(tty_interface_t *state) if (!*selections[i]) continue; char *p = (char *)NULL; - if (strchr(selections[i], 27)) + if (strchr(selections[i], _ESC)) p = decolor_name(selections[i]); printf("%s\n", p ? p : selections[i]); } @@ -202,30 +202,38 @@ free_selections(tty_interface_t *state) selections = (char **)NULL; } -static int isprint_unicode(char c) { +static int +isprint_unicode(char c) +{ return isprint(c) || c & (1 << 7); } -static int is_boundary(char c) { +static int +is_boundary(char c) +{ return ~c & (1 << 7) || c & (1 << 6); } -static void clear(tty_interface_t *state) { +static void +clear(tty_interface_t *state) +{ tty_t *tty = state->tty; tty_setcol(tty, state->options->pad); size_t line = 0; - while (line++ < state->options->num_lines + (state->options->show_info ? 1 : 0)) { + while (line++ < state->options->num_lines + (state->options->show_info ? 1 : 0)) tty_newline(tty); - } + tty_clearline(tty); - if (state->options->num_lines > 0) { + if (state->options->num_lines > 0) tty_moveup(tty, line - 1); - } + tty_flush(tty); } -static void draw_match(tty_interface_t *state, const char *choice, int selected) { +static void +draw_match(tty_interface_t *state, const char *choice, int selected) +{ tty_t *tty = state->tty; options_t *options = state->options; char *search = state->last_search; @@ -269,17 +277,18 @@ static void draw_match(tty_interface_t *state, const char *choice, int selected) } else { tty_setfg(tty, TTY_COLOR_NORMAL); } - if (choice[i] == '\n') { + if (choice[i] == '\n') tty_putc(tty, ' '); - } else { + else tty_printf(tty, "%c", choice[i]); - } } tty_setwrap(tty); tty_setnormal(tty); } -static void draw(tty_interface_t *state) { +static void +draw(tty_interface_t *state) +{ tty_t *tty = state->tty; choices_t *choices = state->choices; options_t *options = state->options; @@ -295,9 +304,47 @@ static void draw(tty_interface_t *state) { } } - int invert = 0; - if (invert == 1) - fprintf(tty->fout, "\x1b[%dB", num_lines + 1); + if (options->reverse == 0) { + tty_setcol(tty, options->pad); + tty_printf(tty, "%s%s", options->prompt, state->search); + tty_clearline(tty); + + if (options->show_info) { + tty_printf(tty, "\n[%lu/%lu]", choices->available, choices->size); + tty_clearline(tty); + } + } + + for (size_t i = start; i < start + num_lines; i++) { + if (options->reverse == 0) + tty_printf(tty, "\n"); + tty_clearline(tty); + const char *choice = choices_get(choices, i); + if (choice) { + int multi_sel = options->multi == 1 && is_selected((char *)choice); + tty_printf(tty, "%*s%s%c%s%c%s", + options->pad, "", colors[POINTER_COLOR], + i == choices->selection ? options->pointer : ' ', + colors[MARKER_COLOR], + multi_sel == 1 ? options->marker : ' ', NC); + draw_match(state, choice, i == choices->selection); + } + if (options->reverse == 1) + tty_printf(tty, "\n"); + } + + if (options->reverse == 0 && num_lines + options->show_info) + tty_moveup(tty, num_lines + options->show_info); + + tty_setcol(tty, options->pad); + tty_printf(tty, "%s%s%s", colors[PROMPT_COLOR], options->prompt, NC); + for (size_t i = 0; i < state->cursor; i++) + fputc(state->search[i], tty->fout); + + if (options->reverse == 0) { + tty_flush(tty); + return; + } tty_setcol(tty, options->pad); tty_printf(tty, "%s%s", options->prompt, state->search); @@ -307,9 +354,42 @@ static void draw(tty_interface_t *state) { tty_printf(tty, "\n[%lu/%lu]", choices->available, choices->size); tty_clearline(tty); } + tty_flush(tty); +} + +/* +static void +draw(tty_interface_t *state) +{ + tty_t *tty = state->tty; + choices_t *choices = state->choices; + options_t *options = state->options; + + unsigned int num_lines = options->num_lines; + size_t start = 0; + size_t current_selection = choices->selection; + if (current_selection + options->scrolloff >= num_lines) { + start = current_selection + options->scrolloff - num_lines + 1; + size_t available = choices_available(choices); + if (start + num_lines >= available && available > 0) { + start = available - num_lines; + } + } - if (invert == 1) - fprintf(tty->fout, "\x1b[%dA", num_lines + 1); + if (options->reverse == 1) // Move to the bottom and print the prompt + tty_printf(tty, "\x1b[%dB", num_lines); + + tty_setcol(tty, options->pad); + tty_printf(tty, "%s%s", options->prompt, state->search); + tty_clearline(tty); + + if (options->show_info) { + tty_printf(tty, "\n[%lu/%lu]", choices->available, choices->size); + tty_clearline(tty); + } + + if (options->reverse == 1) // Go back to the top to print the files list + tty_printf(tty, "\x1b[M\x1b[%dA", num_lines + 1); for (size_t i = start; i < start + num_lines; i++) { tty_printf(tty, "\n"); @@ -317,43 +397,54 @@ static void draw(tty_interface_t *state) { const char *choice = choices_get(choices, i); if (choice) { int multi_sel = options->multi == 1 && is_selected((char *)choice); - tty_printf(tty, "%*s%s%c%s%c%s", options->pad, "", - colors[POINTER_COLOR], i == choices->selection - ? options->pointer : ' ', - colors[MARKER_COLOR], multi_sel == 1 ? - options->marker : ' ', NC); + tty_printf(tty, "%*s%s%c%s%c%s", + options->pad, "", colors[POINTER_COLOR], + i == choices->selection ? options->pointer : ' ', + colors[MARKER_COLOR], + multi_sel == 1 ? options->marker : ' ', NC); draw_match(state, choice, i == choices->selection); } } - if (invert == 0 && num_lines + options->show_info) + if (options->reverse == 0 && num_lines + options->show_info) tty_moveup(tty, num_lines + options->show_info); - if (invert == 1) - fprintf(tty->fout, "%c", '\n'); + if (options->reverse == 1) + tty_printf(tty, "%c", '\n'); tty_setcol(tty, options->pad); - fprintf(tty->fout, "%s%s%s", colors[PROMPT_COLOR], options->prompt, NC); + tty_printf(tty, "%s%s%s", colors[PROMPT_COLOR], options->prompt, NC); for (size_t i = 0; i < state->cursor; i++) fputc(state->search[i], tty->fout); tty_flush(tty); -} +} */ -static void update_search(tty_interface_t *state) { +static void +update_search(tty_interface_t *state) +{ choices_search(state->choices, state->search); strcpy(state->last_search, state->search); } -static void update_state(tty_interface_t *state) { +static void +update_state(tty_interface_t *state) +{ if (strcmp(state->last_search, state->search)) { update_search(state); + if (state->options->reverse == 1) + tty_printf(state->tty, "\x1b[%dA\n", state->options->num_lines + 1); draw(state); } } -static void action_emit(tty_interface_t *state) { +static void +action_emit(tty_interface_t *state) +{ update_state(state); + if (state->options->reverse == 1) + tty_printf(state->tty, "\x1b[%dA\x1b[J", state->options->num_lines); + if (state->options->multi == 1 && seln > 0) { clear(state); tty_close(state->tty); @@ -371,25 +462,24 @@ static void action_emit(tty_interface_t *state) { tty_close(state->tty); const char *selection = choices_get(state->choices, state->choices->selection); - if (selection) { - /* output the selected result */ + if (selection) { /* output the selected result */ char *p = (char *)NULL; - if (strchr(selection, 27)) + if (strchr(selection, _ESC)) p = decolor_name(selection); printf("%s\n", p ? p : selection); - } else { - /* No match, output the query instead */ + } else { /* No match, output the query instead */ printf("%s\n", state->search); } state->exit = EXIT_SUCCESS; } -static void action_del_char(tty_interface_t *state) { - size_t length = strlen(state->search); - if (state->cursor == 0) { +static void +action_del_char(tty_interface_t *state) +{ + if (state->cursor == 0) return; - } + size_t length = strlen(state->search); size_t original_cursor = state->cursor; do { @@ -400,7 +490,9 @@ static void action_del_char(tty_interface_t *state) { length - original_cursor + 1); } -static void action_del_word(tty_interface_t *state) { +static void +action_del_word(tty_interface_t *state) +{ size_t original_cursor = state->cursor; size_t cursor = state->cursor; @@ -415,24 +507,32 @@ static void action_del_word(tty_interface_t *state) { state->cursor = cursor; } -static void action_del_all(tty_interface_t *state) { +static void +action_del_all(tty_interface_t *state) +{ memmove(state->search, &state->search[state->cursor], strlen(state->search) - state->cursor + 1); state->cursor = 0; } -static void action_prev(tty_interface_t *state) { +static void +action_prev(tty_interface_t *state) +{ if (state->options->cycle == 0 && state->choices->selection == 0) return; update_state(state); choices_prev(state->choices); } -static void action_ignore(tty_interface_t *state) { +static void +action_ignore(tty_interface_t *state) +{ (void)state; } -static void action_next(tty_interface_t *state) { +static void +action_next(tty_interface_t *state) +{ if (state->options->cycle == 0 && state->choices->selection + 1 >= state->choices->available) return; @@ -440,14 +540,21 @@ static void action_next(tty_interface_t *state) { choices_next(state->choices); } -static void action_exit(tty_interface_t *state) { +static void +action_exit(tty_interface_t *state) +{ + if (state->options->reverse == 1) + tty_printf(state->tty, "\x1b[%dA\x1b[J", state->options->num_lines); + clear(state); tty_close(state->tty); state->exit = EXIT_FAILURE; } -static void action_left(tty_interface_t *state) { +static void +action_left(tty_interface_t *state) +{ if (state->options->left_aborts == 1) { action_exit(state); return; @@ -460,7 +567,9 @@ static void action_left(tty_interface_t *state) { } } -static void action_right(tty_interface_t *state) { +static void +action_right(tty_interface_t *state) +{ if (state->options->right_accepts == 1) { action_emit(state); return; @@ -473,29 +582,39 @@ static void action_right(tty_interface_t *state) { } } -static void action_beginning(tty_interface_t *state) { +static void +action_beginning(tty_interface_t *state) +{ state->cursor = 0; } -static void action_end(tty_interface_t *state) { +static void +action_end(tty_interface_t *state) +{ state->cursor = strlen(state->search); } -static void action_pageup(tty_interface_t *state) { +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++) choices_prev(state->choices); } -static void action_pagedown(tty_interface_t *state) { +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++) choices_next(state->choices); } -static void action_tab(tty_interface_t *state) { +static void +action_tab(tty_interface_t *state) +{ if (state->options->multi == 1) { action_select(state); action_next(state); @@ -518,7 +637,9 @@ static void action_tab(tty_interface_t *state) { } } -static void append_search(tty_interface_t *state, char ch) { +static void +append_search(tty_interface_t *state, char ch) +{ char *search = state->search; size_t search_size = strlen(search); if (search_size < SEARCH_SIZE_MAX) { @@ -530,8 +651,9 @@ static void append_search(tty_interface_t *state, char ch) { } } -void tty_interface_init(tty_interface_t *state, tty_t *tty, -choices_t *choices, options_t *options) { +void +tty_interface_init(tty_interface_t *state, tty_t *tty, choices_t *choices, options_t *options) +{ state->tty = tty; state->choices = choices; state->options = options; @@ -596,7 +718,9 @@ static const keybinding_t keybindings[] = { #undef KEY_CTRL -static void handle_input(tty_interface_t *state, const char *s, int handle_ambiguous_key) { +static void +handle_input(tty_interface_t *state, const char *s, int handle_ambiguous_key) +{ state->ambiguous_key_pending = 0; char *input = state->input; @@ -645,7 +769,9 @@ static void handle_input(tty_interface_t *state, const char *s, int handle_ambig strcpy(input, ""); } -int tty_interface_run(tty_interface_t *state) { +int +tty_interface_run(tty_interface_t *state) +{ if (state->options->no_color == 0) set_colors(); draw(state); @@ -665,6 +791,8 @@ int tty_interface_run(tty_interface_t *state) { return state->exit; } + if (state->options->reverse == 1) + tty_printf(state->tty, "\x1b[%dA\n", state->options->num_lines + 1); draw(state); } while (tty_input_ready(state->tty, state->ambiguous_key_pending ? KEYTIMEOUT : 0, 0)); From 959ac5794cdd414adc5acda4bdb799a47c241b5d Mon Sep 17 00:00:00 2001 From: leo-arch Date: Thu, 5 May 2022 03:23:31 -0300 Subject: [PATCH 21/28] Update manpage --- fzy.1 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fzy.1 b/fzy.1 index e320bd1..85e3342 100644 --- a/fzy.1 +++ b/fzy.1 @@ -85,6 +85,10 @@ Right arrow key accepts: print selection and exit Left arrow key aborts: cancel selection and exit . .TP +.BR \-\-reverse +List from top, prompt at bottom +. +.TP .BR \-\-no\-color Run colorless . From 1f5c1c762035ae6674f14c69589b7ec984af1097 Mon Sep 17 00:00:00 2001 From: leo-arch Date: Sat, 4 Jun 2022 22:37:39 -0300 Subject: [PATCH 22/28] Fix crash while decoloring output --- src/tty_interface.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tty_interface.c b/src/tty_interface.c index 2cfb121..26b32ce 100644 --- a/src/tty_interface.c +++ b/src/tty_interface.c @@ -119,7 +119,8 @@ decolor_name(const char *name) char *p = buf, *q = buf; size_t i, j = 0; - for (i = 0; name[i]; i++) { + size_t name_len = strlen(name); + for (i = 0; name[i] && i < name_len; i++) { if (name[i] == _ESC && name[i + 1] == '[') { for (j = i + 1; name[j]; j++) { if (name[j] != 'm') From ce60fd939fa79d8f2638831b273793f16ace43fd Mon Sep 17 00:00:00 2001 From: leo-arch Date: Sat, 9 Jul 2022 12:33:53 -0300 Subject: [PATCH 23/28] Update manpage --- fzy.1 | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/fzy.1 b/fzy.1 index 85e3342..ac7dc65 100644 --- a/fzy.1 +++ b/fzy.1 @@ -1,4 +1,4 @@ -.TH FZY 1 "2018-09-23" "fzy 1.0" +.TH FZY 1 "Jul 9, 2022" "fzy 1.0" .SH NAME fzy \- A fuzzy text selector menu for the terminal. .SH SYNOPSIS @@ -96,7 +96,10 @@ Run colorless . .TP .BR "ENTER" -Print the selected items to stdout and exit. +Print the selected items to stdout and exit. If \fI\-\-right\-accepts\fR is set, the Right arrow key performs the same function. Equally, if \fI\-\-tab\-accepts\fR is set, the TAB key performs the same function. +.TP +.BR "ESC" +Exit without printing any result. If \fI\-\-left\-aborts\fR is set, the Left arrow key performs the same function. .TP .BR "Ctrl+c, Ctrl+g, Esc" Exit with status 1, without making a selection. @@ -139,14 +142,14 @@ Interface colors are read from the environment variable \fBFZY_COLORS\fR using a 6 = cyan 7 = white .sp -Use a \fBb\fR before the color to make it bold/bright. A dash (-) means that the color for the interface element in that position must be skipped. +Use a \fBb\fR before the color to make it bold/bright. A dash (\-) means that the color for the interface element in that position must be skipped. .sp -For example, \fBFZF_COLORS="-b1b2-4"\fR is to be read as follows: +For example, \fBFZF_COLORS="\-b1b2\-4"\fR is to be read as follows: .sp - \fB-\fR: no prompt color + \fB\-\fR: no prompt color \fBb1\fR: bold red pointer color \fBb2\fR: bold green marker color - \fB-\fR: no color for the current entry foreground + \fB\-\fR: no color for the current entry foreground \fB4\fR: blue current entry background .sp Default colors are: \fBb6b1b2b40\fR @@ -157,19 +160,21 @@ Default colors are: \fBb6b1b2b40\fR .BR "ls | fzy" Present a menu of items in the current directory .TP -.BR "ls | fzy -l 25" +.BR "ls | fzy \-l 25" Same as above, but show 25 lines of items .TP -.BR "vi $(find -type f | fzy)" +.BR "vi $(find \-type f | fzy)" List files under the current directory and open the one selected in vi. .TP -.BR "cd $(find -type d | fzy)" +.BR "cd $(find \-type d | fzy)" Present all directories under current path, and change to the one selected. .TP .BR "ps aux | fzy | awk '{ print $2 }' | xargs kill" List running processes, kill the selected process .TP -.BR "git checkout $(git branch | cut -c 3- | fzy)" +.BR "git checkout $(git branch | cut \-c 3\- | fzy)" Same as above, but switching git branches. -.SH AUTHOR +.SH AUTHORS John Hawthorn + +L. Abramovich From 00239ab9056e9585e99edfae60a8d1177575bf3e Mon Sep 17 00:00:00 2001 From: leo-arch Date: Sat, 9 Jul 2022 15:46:09 +0000 Subject: [PATCH 24/28] Update README.md --- README.md | 61 +++++++++++++++---------------------------------------- 1 file changed, 16 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index d053934..a3f349c 100644 --- a/README.md +++ b/README.md @@ -2,22 +2,10 @@ **fzy** is a fast, simple fuzzy text selector for the terminal with an advanced scoring algorithm. -[Try it out online!](http://jhawthorn.github.io/fzy-demo) +**Disclaimer**: This fork (originaly intended to make fzy work with [the clifm file manager](https://github.com/leo-arch/clifm)) adds a few new features to the origianl fzy, including basic color support and multi-selection. Consult the manpage for more information. ![](http://i.hawth.ca/u/fzy_animated_demo.svg) -
-It's been kind of life-changing. --@graygilmore -
- -
-fzy works great btw --@alexblackie -
- - [![Build Status](https://github.com/jhawthorn/fzy/workflows/CI/badge.svg)](https://github.com/jhawthorn/fzy/actions) - ## Why use this over fzf, pick, selecta, ctrlp, ...? fzy is faster and shows better results than other fuzzy finders. @@ -34,32 +22,13 @@ Rather than clearing the screen, fzy displays its interface directly below the c ## Installation -**macOS** - -Using Homebrew - - brew install fzy - -Using MacPorts - - sudo port install fzy - -**[Arch Linux](https://www.archlinux.org/packages/?sort=&q=fzy&maintainer=&flagged=)/MSYS2**: `pacman -S fzy` - -**[FreeBSD](https://www.freebsd.org/cgi/ports.cgi?query=fzy&stype=all)**: `pkg install fzy` - -**[Gentoo Linux](https://packages.gentoo.org/packages/app-shells/fzy)**: `emerge -av app-shells/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` - -**[pkgsrc](http://pkgsrc.se/misc/fzy) (NetBSD and others)**: `pkgin install fzy` - -**[openSUSE](https://software.opensuse.org/package/fzy)**: `zypper in fzy` - -### From source - - make - sudo make install +```sh +mkdir build && cd build +git clone https://github.com/leo-arch/fzy +cd fzy +make +sudo make install +``` The `PREFIX` environment variable can be used to specify the install location, the default is `/usr/local`. @@ -98,6 +67,14 @@ nnoremap v :call FzyCommand("ag . --silent -l -g ''", ":vs") nnoremap s :call FzyCommand("ag . --silent -l -g ''", ":sp") ``` +### Use with [clifm](https://github.com/leo-arch/clifm) + +Just run clifm as follows: + +```sh +clifm --fzytab +``` + ## Sorting fzy attempts to present the best matches first. The following considerations are weighted when sorting: @@ -109,9 +86,3 @@ It prefers matching the beginning of words: `amp` is likely to match aabcdef over abc de. It prefers shorter candidates: `test` matches tests over testing. - -## See Also - -* [fzy.js](https://github.com/jhawthorn/fzy.js) Javascript port - - From 81a3197e836bb1b0ea55e3acbe82f8f784c7ee2b Mon Sep 17 00:00:00 2001 From: leo-arch Date: Tue, 14 Feb 2023 20:36:44 -0300 Subject: [PATCH 25/28] Allow lists of only one entry --- src/options.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/options.c b/src/options.c index 9858bcf..f225f50 100644 --- a/src/options.c +++ b/src/options.c @@ -136,10 +136,12 @@ options_parse(options_t *options, int argc, char *argv[]) int l; if (!strcmp(optarg, "max")) { l = INT_MAX; - } else if (sscanf(optarg, "%d", &l) != 1 || l < 3) { +// } else if (sscanf(optarg, "%d", &l) != 1 || l < 3) { + } else if (sscanf(optarg, "%d", &l) != 1 || l < 2) { fprintf(stderr, "Invalid format for --lines: %s\n", optarg); - fprintf(stderr, "Must be integer in range 3..\n"); - usage(argv[0]); + fprintf(stderr, "Must be integer in range 2..\n"); +// fprintf(stderr, "Must be integer in range 3..\n"); +// usage(argv[0]); exit(EXIT_FAILURE); } options->num_lines = l; From 3c10f41003818dfedbca925f75b619d1f30440da Mon Sep 17 00:00:00 2001 From: leo-arch Date: Tue, 14 Feb 2023 20:45:22 -0300 Subject: [PATCH 26/28] Add uninstall target to Makefile --- Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Makefile b/Makefile index 886ef18..9f6a301 100644 --- a/Makefile +++ b/Makefile @@ -45,6 +45,10 @@ install: fzy cp fzy.1 $(DESTDIR)$(MANDIR)/man1/ chmod 644 ${DESTDIR}${MANDIR}/man1/fzy.1 +uninstall: + rm -- $(DESTDIR)$(BINDIR)/fzy + rm -- $(DESTDIR)$(MANDIR)/fzy.1 + fmt: clang-format -i src/*.c src/*.h From b88662605bab9c1fa88f5393c2c4c7228a36a022 Mon Sep 17 00:00:00 2001 From: leo-arch Date: Tue, 6 Jun 2023 00:30:00 -0300 Subject: [PATCH 27/28] Bump version to 1.1 --- Makefile | 17 +++++++-------- config.h | 50 --------------------------------------------- src/bonus.h | 2 +- src/config.def.h | 50 --------------------------------------------- src/fzy.c | 2 +- src/match.c | 2 +- src/options.c | 4 ++-- src/tty.c | 2 +- src/tty_interface.c | 2 +- 9 files changed, 16 insertions(+), 115 deletions(-) delete mode 100644 config.h delete mode 100644 src/config.def.h diff --git a/Makefile b/Makefile index 9f6a301..298e784 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -VERSION=1.0 +VERSION=1.1 CPPFLAGS=-DVERSION=\"${VERSION}\" -D_GNU_SOURCE CFLAGS+=-MD -Wall -Wextra -g -std=c99 -O3 -pedantic -Ideps -Werror=vla @@ -31,11 +31,11 @@ check: test/fzytest fzy: $(OBJECTS) $(CC) $(CFLAGS) $(CCFLAGS) -o $@ $(OBJECTS) $(LIBS) -%.o: %.c config.h - $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< +#%.o: %.c config.h +# $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< -config.h: src/config.def.h - cp src/config.def.h config.h +#config.h: src/config.def.h +# cp src/config.def.h config.h install: fzy mkdir -p $(DESTDIR)$(BINDIR) @@ -55,9 +55,10 @@ fmt: clean: rm -f fzy test/fzytest src/*.o src/*.d deps/*/*.o -veryclean: clean - rm -f config.h +#veryclean: clean +# rm -f config.h -.PHONY: test check all clean veryclean install fmt acceptance +#.PHONY: test check all clean veryclean install fmt acceptance +.PHONY: test check all clean install fmt acceptance -include $(OBJECTS:.o=.d) diff --git a/config.h b/config.h deleted file mode 100644 index f6bb662..0000000 --- a/config.h +++ /dev/null @@ -1,50 +0,0 @@ -#define TTY_COLOR_HIGHLIGHT TTY_COLOR_YELLOW - -#define VERSION "1.0" - -#define SCORE_GAP_LEADING -0.005 -#define SCORE_GAP_TRAILING -0.005 -#define SCORE_GAP_INNER -0.01 -#define SCORE_MATCH_CONSECUTIVE 1.0 -#define SCORE_MATCH_SLASH 0.9 -#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 - -#define DEFAULT_TTY "/dev/tty" -#define DEFAULT_PROMPT "> " -#define DEFAULT_NUM_LINES 10 -#define DEFAULT_WORKERS 0 -#define DEFAULT_SHOW_INFO 0 -#define DEFAULT_DELIMITER '\n' -#define DEFAULT_BENCHMARK 0 -#define DEFAULT_SCORES 0 -#define DEFAULT_SCROLLOFF 0 -#define DEFAULT_FILTER NULL -#define DEFAULT_INIT_SEARCH NULL -#define DEFAULT_MARKER '*' -#define DEFAULT_POINTER '>' -#define DEFAULT_PAD 0 -#define DEFAULT_MULTI 0 -#define DEFAULT_CYCLE 0 -#define DEFAULT_TAB_ACCEPTS 0 -#define DEFAULT_RIGHT_ACCEPTS 0 -#define DEFAULT_LEFT_ABORTS 0 -#define DEFAULT_NO_COLOR 0 -#define DEFAULT_REVERSE 0 - -#define DEFAULT_COLORS "b6b1b2b40" -#define NC "\x1b[0m" /* Reset attributes */ - -/* Color indices: colors (from FZY_COLORS env var) will be parsed - * exactly in this order. See tty_interface.c */ -#define PROMPT_COLOR 0 -#define POINTER_COLOR 1 -#define MARKER_COLOR 2 -#define SEL_FG_COLOR 3 -#define SEL_BG_COLOR 4 -#define COLOR_ITEMS_NUM 5 -#define MAX_COLOR_LEN 48 diff --git a/src/bonus.h b/src/bonus.h index 89cafbe..d0e50d2 100644 --- a/src/bonus.h +++ b/src/bonus.h @@ -1,7 +1,7 @@ #ifndef BONUS_H #define BONUS_H BONUS_H -#include "../config.h" +#include "config.h" #define ASSIGN_LOWER(v) \ ['a'] = (v), \ diff --git a/src/config.def.h b/src/config.def.h deleted file mode 100644 index 393a148..0000000 --- a/src/config.def.h +++ /dev/null @@ -1,50 +0,0 @@ -#define TTY_COLOR_HIGHLIGHT TTY_COLOR_YELLOW - -#define SCORE_GAP_LEADING -0.005 -#define SCORE_GAP_TRAILING -0.005 -#define SCORE_GAP_INNER -0.01 -#define SCORE_MATCH_CONSECUTIVE 1.0 -#define SCORE_MATCH_SLASH 0.9 -#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 - -#define DEFAULT_TTY "/dev/tty" -#define DEFAULT_PROMPT "> " -#define DEFAULT_NUM_LINES 10 -#define DEFAULT_WORKERS 0 -#define DEFAULT_SHOW_INFO 0 -#define DEFAULT_DELIMITER '\n' -#define DEFAULT_BENCHMARK 0 -#define DEFAULT_SCORES 0 -#define DEFAULT_SCROLLOFF 0 -#define DEFAULT_FILTER NULL -#define DEFAULT_INIT_SEARCH NULL -#define DEFAULT_MARKER '*' -#define DEFAULT_POINTER '>' -#define DEFAULT_PAD 0 -#define DEFAULT_MULTI 0 -#define DEFAULT_CYCLE 0 -#define DEFAULT_TAB_ACCEPTS 0 -#define DEFAULT_RIGHT_ACCEPTS 0 -#define DEFAULT_LEFT_ABORTS 0 -#define DEFAULT_NO_COLOR 0 -#define DEFAULT_REVERSE 0 - -#define DEFAULT_COLORS "b6b1b2b40" -#define NC "\x1b[0m" /* Reset attributes */ - -/* Color indices: colors (from FZY_COLORS env var) will be parsed - * exactly in this order. See tty_interface.c */ -#define PROMPT_COLOR 0 -#define POINTER_COLOR 1 -#define MARKER_COLOR 2 -#define SEL_FG_COLOR 3 -#define SEL_BG_COLOR 4 -#define COLOR_ITEMS_NUM 5 -#define MAX_COLOR_LEN 48 - -#define VERSION 1.0 diff --git a/src/fzy.c b/src/fzy.c index 2a0d4fe..aa974bc 100644 --- a/src/fzy.c +++ b/src/fzy.c @@ -11,7 +11,7 @@ #include "options.h" #include "tty_interface.h" -#include "../config.h" +#include "config.h" int main(int argc, char *argv[]) diff --git a/src/match.c b/src/match.c index 0709bb9..f1e2337 100644 --- a/src/match.c +++ b/src/match.c @@ -9,7 +9,7 @@ #include "match.h" #include "bonus.h" -#include "../config.h" +#include "config.h" char * strcasechr(const char *s, char c) diff --git a/src/options.c b/src/options.c index f225f50..fe1f07d 100644 --- a/src/options.c +++ b/src/options.c @@ -6,7 +6,7 @@ #include "options.h" -#include "../config.h" +#include "config.h" static const char *usage_str = "" @@ -101,7 +101,7 @@ options_parse(options_t *options, int argc, char *argv[]) while ((c = getopt_long(argc, argv, "mvhs0e:q:l:t:p:P:j:i", longopts, NULL)) != -1) { switch (c) { case 'v': - printf("%s %s © 2014-2018 John Hawthorn\n", argv[0], VERSION); + printf("%s\n", VERSION); exit(EXIT_SUCCESS); case 's': options->show_scores = 1; break; case '0': options->input_delimiter = '\0'; break; diff --git a/src/tty.c b/src/tty.c index 7450ffe..f48a46a 100644 --- a/src/tty.c +++ b/src/tty.c @@ -11,7 +11,7 @@ #include "tty.h" -#include "../config.h" +#include "config.h" void tty_reset(tty_t *tty) diff --git a/src/tty_interface.c b/src/tty_interface.c index 26b32ce..c6e5c8a 100644 --- a/src/tty_interface.c +++ b/src/tty_interface.c @@ -5,7 +5,7 @@ #include "match.h" #include "tty_interface.h" -#include "../config.h" +#include "config.h" #ifndef PATH_MAX # ifdef __linux__ From e8b1323e311b8eee44ef8a7c6c5dfa7259455cfc Mon Sep 17 00:00:00 2001 From: leo-arch Date: Tue, 6 Jun 2023 00:42:28 -0300 Subject: [PATCH 28/28] Add missing config.h --- .gitignore | 1 - src/config.h | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 src/config.h diff --git a/.gitignore b/.gitignore index 7db22fd..52a4942 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,4 @@ fzy fzytest *.o *.d -config.h test/acceptance/vendor/bundle diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..f2a9fb5 --- /dev/null +++ b/src/config.h @@ -0,0 +1,50 @@ +#define TTY_COLOR_HIGHLIGHT TTY_COLOR_YELLOW + +#define SCORE_GAP_LEADING -0.005 +#define SCORE_GAP_TRAILING -0.005 +#define SCORE_GAP_INNER -0.01 +#define SCORE_MATCH_CONSECUTIVE 1.0 +#define SCORE_MATCH_SLASH 0.9 +#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 + +#define DEFAULT_TTY "/dev/tty" +#define DEFAULT_PROMPT "> " +#define DEFAULT_NUM_LINES 10 +#define DEFAULT_WORKERS 0 +#define DEFAULT_SHOW_INFO 0 +#define DEFAULT_DELIMITER '\n' +#define DEFAULT_BENCHMARK 0 +#define DEFAULT_SCORES 0 +#define DEFAULT_SCROLLOFF 0 +#define DEFAULT_FILTER NULL +#define DEFAULT_INIT_SEARCH NULL +#define DEFAULT_MARKER '*' +#define DEFAULT_POINTER '>' +#define DEFAULT_PAD 0 +#define DEFAULT_MULTI 0 +#define DEFAULT_CYCLE 0 +#define DEFAULT_TAB_ACCEPTS 0 +#define DEFAULT_RIGHT_ACCEPTS 0 +#define DEFAULT_LEFT_ABORTS 0 +#define DEFAULT_NO_COLOR 0 +#define DEFAULT_REVERSE 0 + +#define DEFAULT_COLORS "b6b1b2b40" +#define NC "\x1b[0m" /* Reset attributes */ + +/* Color indices: colors (from FZY_COLORS env var) will be parsed + * exactly in this order. See tty_interface.c */ +#define PROMPT_COLOR 0 +#define POINTER_COLOR 1 +#define MARKER_COLOR 2 +#define SEL_FG_COLOR 3 +#define SEL_BG_COLOR 4 +#define COLOR_ITEMS_NUM 5 +#define MAX_COLOR_LEN 48 + +#define VERSION "1.1"