Codebase list fzy / dc7d235
Import upstream version 1.0+git20201011.1.395a253 Debian Janitor 2 years ago
21 changed file(s) with 356 addition(s) and 127 deletion(s). Raw diff Collapse all Expand all
0 name: CI
1
2 on: [push, pull_request]
3
4 jobs:
5 test:
6 name: Test ${{ matrix.compiler }} on ${{ matrix.os }}
7 strategy:
8 matrix:
9 compiler: [gcc, clang]
10 os: [ubuntu-latest, macOS-latest]
11 runs-on: ${{ matrix.os }}
12 steps:
13 - uses: actions/checkout@v2
14 - name: Compile and run tests
15 run: make && make test
16 env:
17 CC: ${{ matrix.compiler }}
18 test_alpine:
19 name: Test on Alpine Linux
20 runs-on: ubuntu-latest
21 container: docker://alpine
22 steps:
23 - uses: actions/checkout@v2
24 - name: Install build dependencies
25 run: apk add build-base
26 - name: Compile and run tests
27 run: make && make test
28 multiarch_test:
29 name: Test on ${{ matrix.arch }}
30 strategy:
31 matrix:
32 arch: [armv7, aarch64, s390x, ppc64le]
33 runs-on: ubuntu-20.04
34 steps:
35 - uses: actions/checkout@v2
36 - uses: uraimo/run-on-arch-action@v2.0.5
37 name: Compile and run tests
38 id: test
39 with:
40 arch: ${{ matrix.arch }}
41 distro: ubuntu20.04
42 githubToken: ${{ github.token }}
43 install: |
44 apt-get update -q -y
45 apt-get install -y gcc make
46 rm -rf /var/lib/apt/lists/*
47 run: make && make test
48 acceptance_test:
49 name: Acceptance Tests
50 runs-on: ubuntu-latest
51 steps:
52 - uses: actions/checkout@v2
53 - name: Set up Ruby 2.6
54 uses: actions/setup-ruby@v1
55 with:
56 ruby-version: 2.6.x
57 - name: Install dependencies and compile
58 run: |
59 gem install bundler
60 make
61 cd test/acceptance && bundle install --jobs 4 --retry 3
62 - name: Run acceptance tests
63 run: make acceptance
00 fzy
11 fzytest
22 *.o
3 *.d
34 config.h
45 test/acceptance/vendor/bundle
00 dist: trusty
11 sudo: false
22 language: c
3 compiler:
4 - clang
5 - gcc
6 os:
7 - linux
8 - osx
3
4 matrix:
5 include:
6 - os: linux
7 arch: amd64
8 compiler: clang
9 - os: linux
10 arch: ppc64le
11 compiler: clang
12 - os: osx
13 arch: amd64
14 compiler: clang
15 - os: linux
16 arch: amd64
17 compiler: gcc
18 - os: linux
19 arch: ppc64le
20 compiler: gcc
21 - os: osx
22 arch: amd64
23 compiler: gcc
924 script: make && make check
1025 jobs:
1126 include:
00 VERSION=1.0
11
22 CPPFLAGS=-DVERSION=\"${VERSION}\" -D_GNU_SOURCE
3 CFLAGS+=-Wall -Wextra -g -std=c99 -O3 -pedantic -Ideps
3 CFLAGS+=-MD -Wall -Wextra -g -std=c99 -O3 -pedantic -Ideps -Werror=vla
44 PREFIX?=/usr/local
55 MANDIR?=$(PREFIX)/share/man
66 BINDIR?=$(PREFIX)/bin
3333 %.o: %.c config.h
3434 $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
3535
36 config.h:
36 config.h: src/config.def.h
3737 cp src/config.def.h config.h
3838
3939 install: fzy
4848 clang-format -i src/*.c src/*.h
4949
5050 clean:
51 rm -f fzy test/fzytest src/*.o deps/*/*.o
51 rm -f fzy test/fzytest src/*.o src/*.d deps/*/*.o
5252
5353 veryclean: clean
5454 rm -f config.h
5555
5656 .PHONY: test check all clean veryclean install fmt acceptance
57
58 -include $(OBJECTS:.o=.d)
00 ![fzy](http://i.hawth.ca/u/fzy-github.svg)
11
2 **fzy** is a fast, simple fuzzy text selector for the terminal with an advanced [scoring
3 algorithm](#sorting).
2 **fzy** is a fast, simple fuzzy text selector for the terminal with an advanced scoring algorithm.
3
4 [Try it out online!](http://jhawthorn.github.io/fzy-demo)
45
56 ![](http://i.hawth.ca/u/fzy_animated_demo.svg)
67
1415 -<a href="https://twitter.com/alexblackie/status/719297828892188672">@alexblackie</a>
1516 </blockquote>
1617
17 [![Build Status](https://travis-ci.org/jhawthorn/fzy.svg?branch=master)](https://travis-ci.org/jhawthorn/fzy)
18 [![Build Status](https://github.com/jhawthorn/fzy/workflows/CI/badge.svg)](https://github.com/jhawthorn/fzy/actions)
1819
1920 ## Why use this over fzf, pick, selecta, ctrlp, ...?
2021
8384 endif
8485 endfunction
8586
86 nnoremap <leader>e :call FzyCommand("find -type f", ":e")<cr>
87 nnoremap <leader>v :call FzyCommand("find -type f", ":vs")<cr>
88 nnoremap <leader>s :call FzyCommand("find -type f", ":sp")<cr>
87 nnoremap <leader>e :call FzyCommand("find . -type f", ":e")<cr>
88 nnoremap <leader>v :call FzyCommand("find . -type f", ":vs")<cr>
89 nnoremap <leader>s :call FzyCommand("find . -type f", ":sp")<cr>
8990 ```
9091
9192 Any program can be used to filter files presented through fzy. [ag (the silver searcher)](https://github.com/ggreer/the_silver_searcher) can be used to ignore files specified by `.gitignore`.
108109
109110 It prefers shorter candidates: `test` matches <tt><b>test</b>s</tt> over <tt><b>test</b>ing</b></tt>.
110111
112 ## See Also
113
114 * [fzy.js](https://github.com/jhawthorn/fzy.js) Javascript port
115
116
2222 fatal "Unable to create a temporary directory"
2323 fi
2424
25 mkfifo "${PATH_FIFO_IN}" "${PATH_FIFO_OUT}"
25 mkfifo "${PATH_FIFO_IN}" "${PATH_FIFO_OUT}" "${PATH_FIFO_RET}"
2626
2727 export TMUX=$(_echo "${TMUX}" | cut -d , -f 1,2)
2828 eval "tmux \
4040 Non-interactive mode. Print the matches in sorted order for QUERY to stdout.
4141 .
4242 .TP
43 .BR \-0 ", " \-\-read-null
44 Read input delimited by ASCII NUL characters.
45 .
46 .TP
4347 .BR \-h ", " \-\-help
4448 Usage help.
4549 .
5357 .BR "ENTER"
5458 Print the selected item to stdout and exit
5559 .TP
56 .BR "Ctrl+c, Esc"
60 .BR "Ctrl+c, Ctrl+g, Esc"
5761 Exit with status 1, without making a selection.
5862 .TP
5963 .BR "Up Arrow, Ctrl+p"
4545 return buffer;
4646 }
4747
48 void choices_fread(choices_t *c, FILE *file) {
48 void choices_fread(choices_t *c, FILE *file, char input_delimiter) {
4949 /* Save current position for parsing later */
5050 size_t buffer_start = c->buffer_size;
5151
7171 */
7272
7373 /* Tokenize input and add to choices */
74 const char *line_end = c->buffer + c->buffer_size;
7475 char *line = c->buffer + buffer_start;
7576 do {
76 char *nl = strchr(line, '\n');
77 char *nl = strchr(line, input_delimiter);
7778 if (nl)
7879 *nl++ = '\0';
7980
8283 choices_add(c, line);
8384
8485 line = nl;
85 } while (line);
86 } while (line && line < line_end);
8687 }
8788
8889 static void choices_resize(choices_t *c, size_t new_capacity) {
2727 } choices_t;
2828
2929 void choices_init(choices_t *c, options_t *options);
30 void choices_fread(choices_t *c, FILE *file);
30 void choices_fread(choices_t *c, FILE *file, char input_delimiter);
3131 void choices_destroy(choices_t *c);
3232 void choices_add(choices_t *c, const char *choice);
3333 size_t choices_available(choices_t *c);
1010
1111 /* Time (in ms) to wait for additional bytes of an escape sequence */
1212 #define KEYTIMEOUT 25
13
14 #define DEFAULT_TTY "/dev/tty"
15 #define DEFAULT_PROMPT "> "
16 #define DEFAULT_NUM_LINES 10
17 #define DEFAULT_WORKERS 0
18 #define DEFAULT_SHOW_INFO 0
2626 fprintf(stderr, "Must specify -e/--show-matches with --benchmark\n");
2727 exit(EXIT_FAILURE);
2828 }
29 choices_fread(&choices, stdin);
29 choices_fread(&choices, stdin, options.input_delimiter);
3030 for (int i = 0; i < options.benchmark; i++)
3131 choices_search(&choices, options.filter);
3232 } else if (options.filter) {
33 choices_fread(&choices, stdin);
33 choices_fread(&choices, stdin, options.input_delimiter);
3434 choices_search(&choices, options.filter);
3535 for (size_t i = 0; i < choices_available(&choices); i++) {
3636 if (options.show_scores)
4141 /* interactive */
4242
4343 if (isatty(STDIN_FILENO))
44 choices_fread(&choices, stdin);
44 choices_fread(&choices, stdin, options.input_delimiter);
4545
4646 tty_t tty;
4747 tty_init(&tty, options.tty_filename);
4848
4949 if (!isatty(STDIN_FILENO))
50 choices_fread(&choices, stdin);
50 choices_fread(&choices, stdin, options.input_delimiter);
5151
5252 if (options.num_lines > choices.size)
5353 options.num_lines = choices.size;
5454
55 if (options.num_lines + 1 > tty_getheight(&tty))
56 options.num_lines = tty_getheight(&tty) - 1;
55 int num_lines_adjustment = 1;
56 if (options.show_info)
57 num_lines_adjustment++;
58
59 if (options.num_lines + num_lines_adjustment > tty_getheight(&tty))
60 options.num_lines = tty_getheight(&tty) - num_lines_adjustment;
5761
5862 tty_interface_t tty_interface;
5963 tty_interface_init(&tty_interface, &tty, &choices, &options);
33 #include <stdio.h>
44 #include <float.h>
55 #include <math.h>
6 #include <stdlib.h>
67
78 #include "match.h"
89 #include "bonus.h"
2627 return 1;
2728 }
2829
30 #define SWAP(x, y, T) do { T SWAP = x; x = y; y = SWAP; } while (0)
31
2932 #define max(a, b) (((a) > (b)) ? (a) : (b))
3033
31 #ifdef DEBUG_VERBOSE
32 /* print one of the internal matrices */
33 void mat_print(score_t *mat, char name, const char *needle, const char *haystack) {
34 int n = strlen(needle);
35 int m = strlen(haystack);
36 int i, j;
37 fprintf(stderr, "%c ", name);
38 for (j = 0; j < m; j++) {
39 fprintf(stderr, " %c", haystack[j]);
40 }
41 fprintf(stderr, "\n");
42 for (i = 0; i < n; i++) {
43 fprintf(stderr, " %c |", needle[i]);
44 for (j = 0; j < m; j++) {
45 score_t val = mat[i * m + j];
46 if (val == SCORE_MIN) {
47 fprintf(stderr, " -\u221E");
48 } else {
49 fprintf(stderr, " %.3f", val);
50 }
51 }
52 fprintf(stderr, "\n");
53 }
54 fprintf(stderr, "\n\n");
55 }
56 #endif
34 struct match_struct {
35 int needle_len;
36 int haystack_len;
37
38 char lower_needle[MATCH_MAX_LEN];
39 char lower_haystack[MATCH_MAX_LEN];
40
41 score_t match_bonus[MATCH_MAX_LEN];
42 };
5743
5844 static void precompute_bonus(const char *haystack, score_t *match_bonus) {
5945 /* Which positions are beginning of words */
60 int m = strlen(haystack);
6146 char last_ch = '/';
62 for (int i = 0; i < m; i++) {
47 for (int i = 0; haystack[i]; i++) {
6348 char ch = haystack[i];
6449 match_bonus[i] = COMPUTE_BONUS(last_ch, ch);
6550 last_ch = ch;
6651 }
6752 }
6853
54 static void setup_match_struct(struct match_struct *match, const char *needle, const char *haystack) {
55 match->needle_len = strlen(needle);
56 match->haystack_len = strlen(haystack);
57
58 if (match->haystack_len > MATCH_MAX_LEN || match->needle_len > match->haystack_len) {
59 return;
60 }
61
62 for (int i = 0; i < match->needle_len; i++)
63 match->lower_needle[i] = tolower(needle[i]);
64
65 for (int i = 0; i < match->haystack_len; i++)
66 match->lower_haystack[i] = tolower(haystack[i]);
67
68 precompute_bonus(haystack, match->match_bonus);
69 }
70
71 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) {
72 int n = match->needle_len;
73 int m = match->haystack_len;
74 int i = row;
75
76 const char *lower_needle = match->lower_needle;
77 const char *lower_haystack = match->lower_haystack;
78 const score_t *match_bonus = match->match_bonus;
79
80 score_t prev_score = SCORE_MIN;
81 score_t gap_score = i == n - 1 ? SCORE_GAP_TRAILING : SCORE_GAP_INNER;
82
83 for (int j = 0; j < m; j++) {
84 if (lower_needle[i] == lower_haystack[j]) {
85 score_t score = SCORE_MIN;
86 if (!i) {
87 score = (j * SCORE_GAP_LEADING) + match_bonus[j];
88 } else if (j) { /* i > 0 && j > 0*/
89 score = max(
90 last_M[j - 1] + match_bonus[j],
91
92 /* consecutive match, doesn't stack with match_bonus */
93 last_D[j - 1] + SCORE_MATCH_CONSECUTIVE);
94 }
95 curr_D[j] = score;
96 curr_M[j] = prev_score = max(score, prev_score + gap_score);
97 } else {
98 curr_D[j] = SCORE_MIN;
99 curr_M[j] = prev_score = prev_score + gap_score;
100 }
101 }
102 }
103
104 score_t match(const char *needle, const char *haystack) {
105 if (!*needle)
106 return SCORE_MIN;
107
108 struct match_struct match;
109 setup_match_struct(&match, needle, haystack);
110
111 int n = match.needle_len;
112 int m = match.haystack_len;
113
114 if (m > MATCH_MAX_LEN || n > m) {
115 /*
116 * Unreasonably large candidate: return no score
117 * If it is a valid match it will still be returned, it will
118 * just be ranked below any reasonably sized candidates
119 */
120 return SCORE_MIN;
121 } else if (n == m) {
122 /* Since this method can only be called with a haystack which
123 * matches needle. If the lengths of the strings are equal the
124 * strings themselves must also be equal (ignoring case).
125 */
126 return SCORE_MAX;
127 }
128
129 /*
130 * D[][] Stores the best score for this position ending with a match.
131 * M[][] Stores the best possible score at this position.
132 */
133 score_t D[2][MATCH_MAX_LEN], M[2][MATCH_MAX_LEN];
134
135 score_t *last_D, *last_M;
136 score_t *curr_D, *curr_M;
137
138 last_D = D[0];
139 last_M = M[0];
140 curr_D = D[1];
141 curr_M = M[1];
142
143 for (int i = 0; i < n; i++) {
144 match_row(&match, i, curr_D, curr_M, last_D, last_M);
145
146 SWAP(curr_D, last_D, score_t *);
147 SWAP(curr_M, last_M, score_t *);
148 }
149
150 return last_M[m - 1];
151 }
152
69153 score_t match_positions(const char *needle, const char *haystack, size_t *positions) {
70154 if (!*needle)
71155 return SCORE_MIN;
72156
73 int n = strlen(needle);
74 int m = strlen(haystack);
75
76 if (n == m) {
157 struct match_struct match;
158 setup_match_struct(&match, needle, haystack);
159
160 int n = match.needle_len;
161 int m = match.haystack_len;
162
163 if (m > MATCH_MAX_LEN || n > m) {
164 /*
165 * Unreasonably large candidate: return no score
166 * If it is a valid match it will still be returned, it will
167 * just be ranked below any reasonably sized candidates
168 */
169 return SCORE_MIN;
170 } else if (n == m) {
77171 /* Since this method can only be called with a haystack which
78172 * matches needle. If the lengths of the strings are equal the
79173 * strings themselves must also be equal (ignoring case).
84178 return SCORE_MAX;
85179 }
86180
87 if (m > 1024) {
88 /*
89 * Unreasonably large candidate: return no score
90 * If it is a valid match it will still be returned, it will
91 * just be ranked below any reasonably sized candidates
92 */
93 return SCORE_MIN;
94 }
95
96 score_t match_bonus[m];
97 score_t D[n][m], M[n][m];
98
99181 /*
100182 * D[][] Stores the best score for this position ending with a match.
101183 * M[][] Stores the best possible score at this position.
102184 */
103 precompute_bonus(haystack, match_bonus);
185 score_t (*D)[MATCH_MAX_LEN], (*M)[MATCH_MAX_LEN];
186 M = malloc(sizeof(score_t) * MATCH_MAX_LEN * n);
187 D = malloc(sizeof(score_t) * MATCH_MAX_LEN * n);
188
189 score_t *last_D, *last_M;
190 score_t *curr_D, *curr_M;
104191
105192 for (int i = 0; i < n; i++) {
106 score_t prev_score = SCORE_MIN;
107 score_t gap_score = i == n - 1 ? SCORE_GAP_TRAILING : SCORE_GAP_INNER;
108
109 for (int j = 0; j < m; j++) {
110 if (tolower(needle[i]) == tolower(haystack[j])) {
111 score_t score = SCORE_MIN;
112 if (!i) {
113 score = (j * SCORE_GAP_LEADING) + match_bonus[j];
114 } else if (j) { /* i > 0 && j > 0*/
115 score = max(
116 M[i - 1][j - 1] + match_bonus[j],
117
118 /* consecutive match, doesn't stack with match_bonus */
119 D[i - 1][j - 1] + SCORE_MATCH_CONSECUTIVE);
120 }
121 D[i][j] = score;
122 M[i][j] = prev_score = max(score, prev_score + gap_score);
123 } else {
124 D[i][j] = SCORE_MIN;
125 M[i][j] = prev_score = prev_score + gap_score;
126 }
127 }
128 }
129
130 #ifdef DEBUG_VERBOSE
131 fprintf(stderr, "\"%s\" =~ \"%s\"\n", needle, haystack);
132 mat_print(&D[0][0], 'D', needle, haystack);
133 mat_print(&M[0][0], 'M', needle, haystack);
134 fprintf(stderr, "\n");
135 #endif
193 curr_D = &D[i][0];
194 curr_M = &M[i][0];
195
196 match_row(&match, i, curr_D, curr_M, last_D, last_M);
197
198 last_D = curr_D;
199 last_M = curr_M;
200 }
136201
137202 /* backtrace to find the positions of optimal matching */
138203 if (positions) {
163228 }
164229 }
165230
166 return M[n - 1][m - 1];
167 }
168
169 score_t match(const char *needle, const char *haystack) {
170 return match_positions(needle, haystack, NULL);
171 }
231 score_t result = M[n - 1][m - 1];
232
233 free(M);
234 free(D);
235
236 return result;
237 }
66 #define SCORE_MAX INFINITY
77 #define SCORE_MIN -INFINITY
88
9 #define MATCH_MAX_LEN 1024
10
911 int has_match(const char *needle, const char *haystack);
1012 score_t match_positions(const char *needle, const char *haystack, size_t *positions);
1113 score_t match(const char *needle, const char *haystack);
44 #include <string.h>
55
66 #include "options.h"
7
8 #include "../config.h"
79
810 static const char *usage_str =
911 ""
1416 " -e, --show-matches=QUERY Output the sorted matches of QUERY\n"
1517 " -t, --tty=TTY Specify file to use as TTY device (default /dev/tty)\n"
1618 " -s, --show-scores Show the scores of each match\n"
19 " -0, --read-null Read input delimited by ASCII NUL characters\n"
1720 " -j, --workers NUM Use NUM workers for searching. (default is # of CPUs)\n"
21 " -i, --show-info Show selection info line\n"
1822 " -h, --help Display this help and exit\n"
1923 " -v, --version Output version information and exit\n";
2024
2832 {"tty", required_argument, NULL, 't'},
2933 {"prompt", required_argument, NULL, 'p'},
3034 {"show-scores", no_argument, NULL, 's'},
35 {"read-null", no_argument, NULL, '0'},
3136 {"version", no_argument, NULL, 'v'},
3237 {"benchmark", optional_argument, NULL, 'b'},
3338 {"workers", required_argument, NULL, 'j'},
39 {"show-info", no_argument, NULL, 'i'},
3440 {"help", no_argument, NULL, 'h'},
3541 {NULL, 0, NULL, 0}};
3642
3743 void options_init(options_t *options) {
3844 /* set defaults */
39 options->benchmark = 0;
40 options->filter = NULL;
41 options->init_search = NULL;
42 options->tty_filename = "/dev/tty";
43 options->show_scores = 0;
44 options->num_lines = 10;
45 options->scrolloff = 1;
46 options->prompt = "> ";
47 options->workers = 0;
45 options->benchmark = 0;
46 options->filter = NULL;
47 options->init_search = NULL;
48 options->show_scores = 0;
49 options->scrolloff = 1;
50 options->tty_filename = DEFAULT_TTY;
51 options->num_lines = DEFAULT_NUM_LINES;
52 options->prompt = DEFAULT_PROMPT;
53 options->workers = DEFAULT_WORKERS;
54 options->input_delimiter = '\n';
55 options->show_info = DEFAULT_SHOW_INFO;
4856 }
4957
5058 void options_parse(options_t *options, int argc, char *argv[]) {
5159 options_init(options);
5260
5361 int c;
54 while ((c = getopt_long(argc, argv, "vhse:q:l:t:p:j:", longopts, NULL)) != -1) {
62 while ((c = getopt_long(argc, argv, "vhs0e:q:l:t:p:j:i", longopts, NULL)) != -1) {
5563 switch (c) {
5664 case 'v':
5765 printf("%s " VERSION " © 2014-2018 John Hawthorn\n", argv[0]);
5866 exit(EXIT_SUCCESS);
5967 case 's':
6068 options->show_scores = 1;
69 break;
70 case '0':
71 options->input_delimiter = '\0';
6172 break;
6273 case 'q':
6374 options->init_search = optarg;
99110 }
100111 options->num_lines = l;
101112 } break;
113 case 'i':
114 options->show_info = 1;
115 break;
102116 case 'h':
103117 default:
104118 usage(argv[0]);
1010 unsigned int scrolloff;
1111 const char *prompt;
1212 unsigned int workers;
13 char input_delimiter;
14 int show_info;
1315 } options_t;
1416
1517 void options_init(options_t *options);
183183 va_end(args);
184184 }
185185
186 void tty_putc(tty_t *tty, char c) {
187 fputc(c, tty->fout);
188 }
189
186190 void tty_flush(tty_t *tty) {
187191 fflush(tty->fout);
188192 }
5050 void tty_setcol(tty_t *tty, int col);
5151
5252 void tty_printf(tty_t *tty, const char *fmt, ...);
53 void tty_putc(tty_t *tty, char c);
5354 void tty_flush(tty_t *tty);
5455
5556 size_t tty_getwidth(tty_t *tty);
1919
2020 tty_setcol(tty, 0);
2121 size_t line = 0;
22 while (line++ < state->options->num_lines) {
22 while (line++ < state->options->num_lines + (state->options->show_info ? 1 : 0)) {
2323 tty_newline(tty);
2424 }
2525 tty_clearline(tty);
3535 char *search = state->last_search;
3636
3737 int n = strlen(search);
38 size_t positions[n + 1];
39 for (int i = 0; i < n + 1; i++)
38 size_t positions[MATCH_MAX_LEN];
39 for (int i = 0; i < n + 1 && i < MATCH_MAX_LEN; i++)
4040 positions[i] = -1;
4141
4242 score_t score = match_positions(search, choice, &positions[0]);
6464 } else {
6565 tty_setfg(tty, TTY_COLOR_NORMAL);
6666 }
67 tty_printf(tty, "%c", choice[i]);
67 if (choice[i] == '\n') {
68 tty_putc(tty, ' ');
69 } else {
70 tty_printf(tty, "%c", choice[i]);
71 }
6872 }
6973 tty_setwrap(tty);
7074 tty_setnormal(tty);
8589 start = available - num_lines;
8690 }
8791 }
92
8893 tty_setcol(tty, 0);
8994 tty_printf(tty, "%s%s", options->prompt, state->search);
9095 tty_clearline(tty);
96
97 if (options->show_info) {
98 tty_printf(tty, "\n[%lu/%lu]", choices->available, choices->size);
99 tty_clearline(tty);
100 }
101
91102 for (size_t i = start; i < start + num_lines; i++) {
92103 tty_printf(tty, "\n");
93104 tty_clearline(tty);
96107 draw_match(state, choice, i == choices->selection);
97108 }
98109 }
99 if (num_lines > 0) {
100 tty_moveup(tty, num_lines);
101 }
110
111 if (num_lines + options->show_info)
112 tty_moveup(tty, num_lines + options->show_info);
102113
103114 tty_setcol(tty, 0);
104115 fputs(options->prompt, tty->fout);
286297 {KEY_CTRL('I'), action_autocomplete}, /* TAB (C-I ) */
287298 {KEY_CTRL('C'), action_exit}, /* C-C */
288299 {KEY_CTRL('D'), action_exit}, /* C-D */
300 {KEY_CTRL('G'), action_exit}, /* C-G */
289301 {KEY_CTRL('M'), action_emit}, /* CR */
290302 {KEY_CTRL('P'), action_prev}, /* C-P */
291303 {KEY_CTRL('N'), action_next}, /* C-N */
0 # coding: utf-8
01 require 'minitest'
12 require 'minitest/autorun'
23 require 'ttytest'
443444 LongStringOfTextLongStringOfTextLongStringOfTextLongStringOfTextLongStringOfText
444445 LongStringOfTextLongStringOfTextLongStri
445446 TTY
447 end
448
449 def test_show_info
450 @tty = interactive_fzy(input: %w[foo bar baz], args: "-i")
451 @tty.assert_matches ">\n[3/3]\nfoo\nbar\nbaz"
452 @tty.send_keys("ba")
453 @tty.assert_matches "> ba\n[2/3]\nbar\nbaz"
454 @tty.send_keys("q")
455 @tty.assert_matches "> baq\n[0/3]"
446456 end
447457
448458 def test_help
455465 -e, --show-matches=QUERY Output the sorted matches of QUERY
456466 -t, --tty=TTY Specify file to use as TTY device (default /dev/tty)
457467 -s, --show-scores Show the scores of each match
468 -0, --read-null Read input delimited by ASCII NUL characters
458469 -j, --workers NUM Use NUM workers for searching. (default is # of CPUs)
470 -i, --show-info Show selection info line
459471 -h, --help Display this help and exit
460472 -v, --version Output version information and exit
461473 TTY
134134 }
135135
136136 TEST test_choices_large_input() {
137 int N = 100000;
138 char *strings[N];
137 const int N = 100000;
138 char *strings[100000];
139139
140140 for(int i = 0; i < N; i++) {
141141 asprintf(&strings[i], "%i", i);
127127 ASSERT_SCORE_EQ(SCORE_GAP_LEADING + SCORE_MATCH_DOT, match("a", ".a"));
128128 ASSERT_SCORE_EQ(SCORE_GAP_LEADING*3 + SCORE_MATCH_DOT, match("a", "*a.a"));
129129 ASSERT_SCORE_EQ(SCORE_GAP_LEADING + SCORE_GAP_INNER + SCORE_MATCH_DOT, match("a", "*a.a"));
130 PASS();
131 }
132
133 TEST score_long_string() {
134 char string[4096];
135 memset(string, 'a', sizeof(string) - 1);
136 string[sizeof(string) - 1] = '\0';
137
138 ASSERT_SCORE_EQ(SCORE_MIN, match("aa", string));
139 ASSERT_SCORE_EQ(SCORE_MIN, match(string, "aa"));
140 ASSERT_SCORE_EQ(SCORE_MIN, match(string, string));
141
130142 PASS();
131143 }
132144
209221 RUN_TEST(score_slash);
210222 RUN_TEST(score_capital);
211223 RUN_TEST(score_dot);
224 RUN_TEST(score_long_string);
212225
213226 RUN_TEST(positions_consecutive);
214227 RUN_TEST(positions_start_of_word);