1 #include "config.h"
2
3 #include <sys/ioctl.h>
4
5 #include <ctype.h>
6 #include <err.h>
7 #include <limits.h>
8 #include <locale.h>
9 #include <poll.h>
10 #include <signal.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <termios.h>
15 #include <unistd.h>
16 #include <wchar.h>
17 #include <wctype.h>
18
19 #define tty_putp(capability, fatal) do { \
20 if (tputs((capability), 1, tty_putc) == ERR && (fatal)) \
21 errx(1, #capability ": unknown terminfo capability"); \
22 } while (0)
23
24 enum key {
25 UNKNOWN,
26 ALT_ENTER,
27 BACKSPACE,
28 DEL,
29 ENTER,
30 CTRL_A,
31 CTRL_C,
32 CTRL_E,
33 CTRL_K,
34 CTRL_L,
35 CTRL_O,
36 CTRL_U,
37 CTRL_W,
38 CTRL_Z,
39 RIGHT,
40 LEFT,
41 LINE_DOWN,
42 LINE_UP,
43 PAGE_DOWN,
44 PAGE_UP,
45 END,
46 HOME,
47 PRINTABLE
48 };
49
50 struct choice {
51 const char *description;
52 const char *string;
53 size_t length;
54 ssize_t match_start; /* inclusive match start offset */
55 ssize_t match_end; /* exclusive match end offset */
56 double score;
57 };
58
59 static int choicecmp(const void *, const void *);
60 static void delete_between(char *, size_t, size_t, size_t);
61 static char *eager_strpbrk(const char *, const char *);
62 static int filter_choices(size_t);
63 static char *get_choices(void);
64 static enum key get_key(const char **);
65 static void handle_sigwinch(int);
66 static int isu8cont(unsigned char);
67 static int isu8start(unsigned char);
68 static int isword(const char *);
69 static size_t min_match(const char *, size_t, ssize_t *,
70 ssize_t *);
71 static size_t print_choices(size_t, size_t);
72 static void print_line(const char *, size_t, int, ssize_t,
73 ssize_t);
74 static const struct choice *selected_choice(void);
75 static size_t skipescseq(const char *);
76 static const char *strcasechr(const char *, const char *);
77 static void toggle_sigwinch(int);
78 static int tty_getc(void);
79 static const char *tty_getcap(char *);
80 static void tty_init(int);
81 static const char *tty_parm1(char *, int);
82 static int tty_putc(int);
83 static void tty_restore(int);
84 static void tty_size(void);
85 static __dead void usage(void);
86 static int xmbtowc(wchar_t *, const char *);
87
88 static struct termios tio;
89 static struct {
90 size_t size;
91 size_t length;
92 struct choice *v;
93 } choices;
94 static FILE *tty_in, *tty_out;
95 static char *query;
96 static size_t query_length, query_size;
97 static volatile sig_atomic_t gotsigwinch;
98 static unsigned int choices_lines, tty_columns, tty_lines;
99 static int descriptions;
100 static int sort = 1;
101 static int use_alternate_screen = 1;
102 static int use_keypad = 1;
103
104 int
main(int argc,char * argv[])105 main(int argc, char *argv[])
106 {
107 const struct choice *choice;
108 char *input;
109 int c;
110 int output_description = 0;
111 int rc = 0;
112
113 setlocale(LC_CTYPE, "");
114
115 if (pledge("stdio tty rpath wpath cpath", NULL) == -1)
116 err(1, "pledge");
117
118 while ((c = getopt(argc, argv, "doq:KSxX")) != -1)
119 switch (c) {
120 case 'd':
121 descriptions = 1;
122 break;
123 case 'K':
124 use_keypad = 0;
125 break;
126 case 'o':
127 /*
128 * Only output description if descriptions are read and
129 * displayed in the list of choices.
130 */
131 output_description = descriptions;
132 break;
133 case 'q':
134 if ((query = strdup(optarg)) == NULL)
135 err(1, "strdup");
136 query_length = strlen(query);
137 query_size = query_length + 1;
138 break;
139 case 'S':
140 sort = 0;
141 break;
142 case 'x':
143 use_alternate_screen = 1;
144 break;
145 case 'X':
146 use_alternate_screen = 0;
147 break;
148 default:
149 usage();
150 }
151 argc -= optind;
152 argv += optind;
153 if (argc > 0)
154 usage();
155
156 if (query == NULL) {
157 query_size = 64;
158 if ((query = calloc(query_size, sizeof(char))) == NULL)
159 err(1, NULL);
160 }
161
162 input = get_choices();
163 tty_init(1);
164
165 if (pledge("stdio tty", NULL) == -1)
166 err(1, "pledge");
167
168 choice = selected_choice();
169 tty_restore(1);
170 if (choice != NULL) {
171 printf("%s\n", choice->string);
172 if (output_description)
173 printf("%s\n", choice->description);
174 } else {
175 rc = 1;
176 }
177
178 free(input);
179 free(choices.v);
180 free(query);
181
182 return rc;
183 }
184
185 __dead void
usage(void)186 usage(void)
187 {
188 fprintf(stderr, "usage: pick [-dKoSXx] [-q query]\n");
189 exit(1);
190 }
191
192 char *
get_choices(void)193 get_choices(void)
194 {
195 char *buf, *description, *ifs, *start, *stop;
196 ssize_t n;
197 size_t length = 0;
198 size_t size = BUFSIZ;
199
200 if ((ifs = getenv("IFS")) == NULL || *ifs == '\0')
201 ifs = " ";
202
203 if ((buf = malloc(size)) == NULL)
204 err(1, NULL);
205 for (;;) {
206 if ((n = read(STDIN_FILENO, buf + length, size - length)) == -1)
207 err(1, "read");
208 else if (n == 0)
209 break;
210
211 length += n;
212 if (length + 1 < size)
213 continue;
214 if ((buf = reallocarray(buf, 2, size)) == NULL)
215 err(1, NULL);
216 size *= 2;
217 }
218 buf[length] = '\0';
219
220 choices.size = 16;
221 if ((choices.v = reallocarray(NULL, choices.size,
222 sizeof(struct choice))) == NULL)
223 err(1, NULL);
224
225 start = buf;
226 while ((stop = strchr(start, '\n')) != NULL) {
227 *stop = '\0';
228
229 if (descriptions && (description = eager_strpbrk(start, ifs)))
230 *description++ = '\0';
231 else
232 description = "";
233
234 choices.v[choices.length].length = stop - start;
235 choices.v[choices.length].string = start;
236 choices.v[choices.length].description = description;
237 choices.v[choices.length].match_start = -1;
238 choices.v[choices.length].match_end = -1;
239 choices.v[choices.length].score = 0;
240
241 start = stop + 1;
242
243 /* Ensure room for a extra choice when ALT_ENTER is invoked. */
244 if (++choices.length + 1 < choices.size)
245 continue;
246 choices.size *= 2;
247 if ((choices.v = reallocarray(choices.v, choices.size,
248 sizeof(struct choice))) == NULL)
249 err(1, NULL);
250 }
251
252 return buf;
253 }
254
255 char *
eager_strpbrk(const char * string,const char * separators)256 eager_strpbrk(const char *string, const char *separators)
257 {
258 char *tmp_ptr;
259 char *ptr = NULL;
260
261 for (tmp_ptr = strpbrk(string, separators);
262 tmp_ptr;
263 tmp_ptr = strpbrk(tmp_ptr, separators))
264 ptr = tmp_ptr++;
265
266 return ptr;
267 }
268
269 const struct choice *
selected_choice(void)270 selected_choice(void)
271 {
272 const char *buf;
273 size_t cursor_position, i, j, length, xscroll;
274 size_t choices_count = 0;
275 size_t selection = 0;
276 size_t yscroll = 0;
277 int dochoices = 0;
278 int dofilter = 1;
279 int query_grew = 0;
280
281 cursor_position = query_length;
282
283 for (;;) {
284 /*
285 * If the user didn't add more characters to the query all
286 * choices have to be reconsidered as potential matches.
287 * In the opposite scenario, there's no point in reconsidered
288 * all choices again since the ones that didn't match the
289 * previous query will clearly not match the current one due to
290 * the fact that previous query is a left-most substring of the
291 * current one.
292 */
293 if (!query_grew)
294 choices_count = choices.length;
295 query_grew = 0;
296 if (dofilter) {
297 if ((dochoices = filter_choices(choices_count)))
298 dofilter = selection = yscroll = 0;
299 }
300
301 tty_putp(cursor_invisible, 0);
302 tty_putp(carriage_return, 1); /* move cursor to first column */
303 if (cursor_position >= tty_columns)
304 xscroll = cursor_position - tty_columns + 1;
305 else
306 xscroll = 0;
307 print_line(&query[xscroll], query_length - xscroll, 0, -1, -1);
308 if (dochoices) {
309 if (selection - yscroll >= choices_lines)
310 yscroll = selection - choices_lines + 1;
311 choices_count = print_choices(yscroll, selection);
312 }
313 tty_putp(carriage_return, 1); /* move cursor to first column */
314 for (i = j = 0; i < cursor_position; j++)
315 while (isu8cont(query[++i]))
316 continue;
317 if (j > 0)
318 /*
319 * parm_right_cursor interprets 0 as 1, therefore only
320 * move the cursor if the position is non zero.
321 */
322 tty_putp(tty_parm1(parm_right_cursor, j), 1);
323 tty_putp(cursor_normal, 0);
324 fflush(tty_out);
325
326 switch (get_key(&buf)) {
327 case ENTER:
328 if (choices_count > 0)
329 return &choices.v[selection];
330 break;
331 case ALT_ENTER:
332 choices.v[choices.length].string = query;
333 choices.v[choices.length].description = "";
334 return &choices.v[choices.length];
335 case CTRL_C:
336 return NULL;
337 case CTRL_Z:
338 tty_restore(0);
339 kill(getpid(), SIGTSTP);
340 tty_init(0);
341 break;
342 case BACKSPACE:
343 if (cursor_position > 0) {
344 for (length = 1;
345 isu8cont(query[cursor_position - length]);
346 length++)
347 continue;
348 delete_between(query, query_length,
349 cursor_position - length, cursor_position);
350 cursor_position -= length;
351 query_length -= length;
352 dofilter = 1;
353 }
354 break;
355 case DEL:
356 if (cursor_position < query_length) {
357 for (length = 1;
358 isu8cont(query[cursor_position + length]);
359 length++)
360 continue;
361 delete_between(query, query_length,
362 cursor_position, cursor_position + length);
363 query_length -= length;
364 dofilter = 1;
365 }
366 break;
367 case CTRL_U:
368 delete_between(query, query_length, 0, cursor_position);
369 query_length -= cursor_position;
370 cursor_position = 0;
371 dofilter = 1;
372 break;
373 case CTRL_K:
374 delete_between(query, query_length, cursor_position,
375 query_length);
376 query_length = cursor_position;
377 dofilter = 1;
378 break;
379 case CTRL_L:
380 tty_size();
381 break;
382 case CTRL_O:
383 sort = !sort;
384 dofilter = 1;
385 break;
386 case CTRL_W:
387 if (cursor_position == 0)
388 break;
389
390 for (i = cursor_position; i > 0;) {
391 while (isu8cont(query[--i]))
392 continue;
393 if (isword(query + i))
394 break;
395 }
396 for (j = i; i > 0; i = j) {
397 while (isu8cont(query[--j]))
398 continue;
399 if (!isword(query + j))
400 break;
401 }
402 delete_between(query, query_length, i, cursor_position);
403 query_length -= cursor_position - i;
404 cursor_position = i;
405 dofilter = 1;
406 break;
407 case CTRL_A:
408 cursor_position = 0;
409 break;
410 case CTRL_E:
411 cursor_position = query_length;
412 break;
413 case LINE_DOWN:
414 if (selection < choices_count - 1) {
415 selection++;
416 if (selection - yscroll == choices_lines)
417 yscroll++;
418 }
419 break;
420 case LINE_UP:
421 if (selection > 0) {
422 selection--;
423 if (yscroll > selection)
424 yscroll--;
425 }
426 break;
427 case LEFT:
428 while (cursor_position > 0
429 && isu8cont(query[--cursor_position]))
430 continue;
431 break;
432 case RIGHT:
433 while (cursor_position < query_length
434 && isu8cont(query[++cursor_position]))
435 continue;
436 break;
437 case PAGE_DOWN:
438 if (selection + choices_lines < choices_count)
439 yscroll = selection += choices_lines;
440 else
441 selection = choices_count - 1;
442 break;
443 case PAGE_UP:
444 if (selection > choices_lines)
445 yscroll = selection -= choices_lines;
446 else
447 yscroll = selection = 0;
448 break;
449 case END:
450 if (choices_count > 0)
451 selection = choices_count - 1;
452 break;
453 case HOME:
454 yscroll = selection = 0;
455 break;
456 case PRINTABLE:
457 length = strlen(buf);
458
459 if (query_length + length >= query_size) {
460 query_size = 2*query_length + length;
461 if ((query = reallocarray(query, query_size,
462 sizeof(char))) == NULL)
463 err(1, NULL);
464 }
465
466 if (cursor_position < query_length)
467 memmove(query + cursor_position + length,
468 query + cursor_position,
469 query_length - cursor_position);
470
471 memcpy(query + cursor_position, buf, length);
472 cursor_position += length;
473 query_length += length;
474 query[query_length] = '\0';
475 dofilter = query_grew = 1;
476 break;
477 case UNKNOWN:
478 break;
479 }
480 }
481 }
482
483 /*
484 * Filter the first nchoices number of choices using the current query and
485 * regularly check for new user input in order to abort filtering. This
486 * improves the performance when the cardinality of the choices is large.
487 * Returns non-zero if the filtering was not aborted.
488 */
489 int
filter_choices(size_t nchoices)490 filter_choices(size_t nchoices)
491 {
492 struct choice *c;
493 struct pollfd pfd;
494 size_t i, match_length;
495 int nready;
496
497 for (i = 0; i < nchoices; i++) {
498 c = &choices.v[i];
499 if (min_match(c->string, 0,
500 &c->match_start, &c->match_end) == INT_MAX) {
501 c->match_start = c->match_end = -1;
502 c->score = 0;
503 } else if (!sort) {
504 c->score = 1;
505 } else {
506 match_length = c->match_end - c->match_start;
507 c->score = (double)query_length/match_length/c->length;
508 }
509
510 if (i > 0 && i % 50 == 0) {
511 pfd.fd = fileno(tty_in);
512 pfd.events = POLLIN;
513 if ((nready = poll(&pfd, 1, 0)) == -1)
514 err(1, "poll");
515 if (nready == 1 && pfd.revents & (POLLIN | POLLHUP))
516 return 0;
517 }
518 }
519 qsort(choices.v, nchoices, sizeof(struct choice), choicecmp);
520
521 return 1;
522 }
523
524 int
choicecmp(const void * p1,const void * p2)525 choicecmp(const void *p1, const void *p2)
526 {
527 const struct choice *c1, *c2;
528
529 c1 = p1;
530 c2 = p2;
531 if (c1->score < c2->score)
532 return 1;
533 if (c1->score > c2->score)
534 return -1;
535 /*
536 * The two choices have an equal score.
537 * Sort based on the address of string since it reflects the initial
538 * input order.
539 * The comparison is inverted since the choice with the lowest address
540 * must come first.
541 */
542 if (c1->string < c2->string)
543 return -1;
544 if (c1->string > c2->string)
545 return 1;
546 return 0;
547 }
548
549 size_t
min_match(const char * string,size_t offset,ssize_t * start,ssize_t * end)550 min_match(const char *string, size_t offset, ssize_t *start, ssize_t *end)
551 {
552 const char *e, *q, *s;
553 size_t length;
554
555 q = query;
556 if (*q == '\0' || (s = e = strcasechr(&string[offset], q)) == NULL)
557 return INT_MAX;
558
559 for (;;) {
560 for (e++, q++; isu8cont(*q); e++, q++)
561 continue;
562 if (*q == '\0')
563 break;
564 if ((e = strcasechr(e, q)) == NULL)
565 return INT_MAX;
566 }
567
568 length = e - s;
569 /* LEQ is used to obtain the shortest left-most match. */
570 if (length == query_length
571 || length <= min_match(string, s - string + 1, start, end)) {
572 *start = s - string;
573 *end = e - string;
574 }
575
576 return *end - *start;
577 }
578
579 /*
580 * Returns a pointer to first occurrence of the first character in s2 in s1 with
581 * respect to Unicode characters disregarding case.
582 */
583 const char *
strcasechr(const char * s1,const char * s2)584 strcasechr(const char *s1, const char *s2)
585 {
586 wchar_t wc1, wc2;
587 size_t i;
588 int nbytes;
589
590 if (xmbtowc(&wc2, s2) == 0)
591 return NULL;
592
593 for (i = 0; s1[i] != '\0';) {
594 if ((nbytes = skipescseq(s1 + i)) > 0)
595 /* A match inside an escape sequence is ignored. */;
596 else if ((nbytes = xmbtowc(&wc1, s1 + i)) == 0)
597 nbytes = 1;
598 else if (wcsncasecmp(&wc1, &wc2, 1) == 0)
599 return s1 + i;
600 i += nbytes;
601 }
602
603 return NULL;
604 }
605
606 /*
607 * Returns the length of a CSI or OSC escape sequence located at the beginning
608 * of str.
609 */
610 size_t
skipescseq(const char * str)611 skipescseq(const char *str)
612 {
613 size_t i;
614 int csi = 0;
615 int osc = 0;
616
617 if (str[0] == '\033' && str[1] == '[')
618 csi = 1;
619 else if (str[0] == '\033' && str[1] == ']')
620 osc = 1;
621 else
622 return 0;
623
624 for (i = 2; str[i] != '\0'; i++)
625 if ((csi && str[i] >= '@' && str[i] <= '~') ||
626 (osc && str[i] == '\a'))
627 break;
628
629 return i + 1;
630 }
631
632 void
tty_init(int doinit)633 tty_init(int doinit)
634 {
635 struct termios new_attributes;
636
637 if (doinit && (tty_in = fopen("/dev/tty", "r")) == NULL)
638 err(1, "fopen");
639
640 tcgetattr(fileno(tty_in), &tio);
641 new_attributes = tio;
642 new_attributes.c_iflag |= ICRNL; /* map CR to NL */
643 new_attributes.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
644 new_attributes.c_cc[VMIN] = 1;
645 new_attributes.c_cc[VTIME] = 0;
646 new_attributes.c_cc[VDISCARD] = _POSIX_VDISABLE;
647 tcsetattr(fileno(tty_in), TCSANOW, &new_attributes);
648
649 if (doinit && (tty_out = fopen("/dev/tty", "w")) == NULL)
650 err(1, "fopen");
651
652 if (doinit)
653 setupterm((char *)0, fileno(tty_out), (int *)0);
654
655 tty_size();
656
657 if (use_keypad)
658 tty_putp(keypad_xmit, 0);
659 if (use_alternate_screen)
660 tty_putp(enter_ca_mode, 0);
661
662 toggle_sigwinch(0);
663 }
664
665 int
tty_putc(int c)666 tty_putc(int c)
667 {
668 if (putc(c, tty_out) == EOF)
669 err(1, "putc");
670
671 return c;
672 }
673
674 void
handle_sigwinch(int sig)675 handle_sigwinch(int sig)
676 {
677 gotsigwinch = sig == SIGWINCH;
678 }
679
680 void
toggle_sigwinch(int enable)681 toggle_sigwinch(int enable)
682 {
683 struct sigaction sa;
684
685 sa.sa_flags = 0;
686 sa.sa_handler = enable ? handle_sigwinch : SIG_IGN;
687 sigemptyset(&sa.sa_mask);
688
689 if (sigaction(SIGWINCH, &sa, NULL) == -1)
690 err(1, "sigaction: SIGWINCH");
691 }
692
693 void
tty_restore(int doclose)694 tty_restore(int doclose)
695 {
696 tcsetattr(fileno(tty_in), TCSANOW, &tio);
697 if (doclose)
698 fclose(tty_in);
699
700 tty_putp(carriage_return, 1); /* move cursor to first column */
701 tty_putp(clr_eos, 1);
702
703 if (use_keypad)
704 tty_putp(keypad_local, 0);
705 if (use_alternate_screen)
706 tty_putp(exit_ca_mode, 0);
707
708 if (doclose)
709 fclose(tty_out);
710 else
711 fflush(tty_out);
712 }
713
714 void
tty_size(void)715 tty_size(void)
716 {
717 const char *cp;
718 struct winsize ws;
719 int sz;
720
721 if (ioctl(fileno(tty_in), TIOCGWINSZ, &ws) != -1) {
722 tty_columns = ws.ws_col;
723 tty_lines = ws.ws_row;
724 }
725
726 if (tty_columns == 0 && (sz = tigetnum("cols")) > 0)
727 tty_columns = sz;
728 if (tty_lines == 0 && (sz = tigetnum("lines")) > 0)
729 tty_lines = sz;
730
731 if ((cp = getenv("COLUMNS")) != NULL &&
732 (sz = strtonum(cp, 1, INT_MAX, NULL)) > 0)
733 tty_columns = sz;
734 if ((cp = getenv("LINES")) != NULL &&
735 (sz = strtonum(cp, 1, INT_MAX, NULL)) > 0)
736 tty_lines = sz;
737
738 if (tty_columns == 0)
739 tty_columns = 80;
740 if (tty_lines == 0)
741 tty_lines = 24;
742
743 choices_lines = tty_lines - 1; /* available lines, minus query line */
744 }
745
746 void
print_line(const char * str,size_t len,int standout,ssize_t enter_underline,ssize_t exit_underline)747 print_line(const char *str, size_t len, int standout,
748 ssize_t enter_underline, ssize_t exit_underline)
749 {
750 size_t i;
751 wchar_t wc;
752 unsigned int col;
753 int nbytes, width;
754
755 if (standout)
756 tty_putp(enter_standout_mode, 1);
757
758 col = i = 0;
759 while (col < tty_columns) {
760 if (enter_underline == (ssize_t)i)
761 tty_putp(enter_underline_mode, 1);
762 else if (exit_underline == (ssize_t)i)
763 tty_putp(exit_underline_mode, 1);
764 if (i == len)
765 break;
766
767 if (str[i] == '\t') {
768 width = 8 - (col & 7); /* ceil to multiple of 8 */
769 if (col + width > tty_columns)
770 break;
771 col += width;
772
773 for (; width > 0; width--)
774 tty_putc(' ');
775
776 i++;
777 continue;
778 }
779
780 /*
781 * A NUL will be present prior the NUL-terminator if
782 * descriptions are enabled.
783 */
784 if (str[i] == '\0') {
785 tty_putc(' ');
786 i++;
787 col++;
788 continue;
789 }
790
791 /*
792 * Output every character, even invalid ones and escape
793 * sequences. Even tho they don't occupy any columns.
794 */
795 if ((nbytes = skipescseq(str + i)) > 0) {
796 width = 0;
797 } else if ((nbytes = xmbtowc(&wc, str + i)) == 0) {
798 nbytes = 1;
799 width = 0;
800 } else if ((width = wcwidth(wc)) < 0) {
801 width = 0;
802 }
803
804 if (col + width > tty_columns)
805 break;
806 col += width;
807
808 for (; nbytes > 0; nbytes--, i++)
809 tty_putc(str[i]);
810 }
811 for (; col < tty_columns; col++)
812 tty_putc(' ');
813
814 /*
815 * If exit_underline is greater than columns the underline attribute
816 * will spill over on the next line unless all attributes are exited.
817 */
818 tty_putp(exit_attribute_mode, 1);
819 }
820
821 /*
822 * Output as many choices as possible starting from offset and return the number
823 * of choices with a positive score. If the query is empty, all choices are
824 * considered having a positive score.
825 */
826 size_t
print_choices(size_t offset,size_t selection)827 print_choices(size_t offset, size_t selection)
828 {
829 const struct choice *choice;
830 size_t i;
831
832 for (i = offset; i < choices.length; i++) {
833 choice = choices.v + i;
834 if (choice->score == 0 && query_length > 0)
835 break;
836
837 if (i - offset < choices_lines)
838 print_line(choice->string, choice->length,
839 i == selection, choice->match_start,
840 choice->match_end);
841 }
842
843 if (i - offset < choices.length && i - offset < choices_lines) {
844 /*
845 * Printing the choices did not consume all available
846 * lines and there could still be choices left from the
847 * last print in the lines not yet consumed.
848 *
849 * The clr_eos capability clears the screen from the
850 * current column to the end. If the last visible choice
851 * is selected, the standout in the last and current
852 * column will be also be cleared. Therefore, move down
853 * one line before clearing the screen.
854 */
855 tty_putc('\n');
856 tty_putp(clr_eos, 1);
857 tty_putp(tty_parm1(parm_up_cursor, (i - offset) + 1), 1);
858 } else if (i > 0) {
859 /*
860 * parm_up_cursor interprets 0 as 1, therefore only move
861 * upwards if any choices where printed.
862 */
863 tty_putp(tty_parm1(parm_up_cursor,
864 i < choices_lines ? i : choices_lines), 1);
865 }
866
867 return i;
868 }
869
870 enum key
get_key(const char ** key)871 get_key(const char **key)
872 {
873 #define CAP(k, s) { k, s, NULL, 0, -1 }
874 #define KEY(k, s) { k, NULL, s, sizeof(s) - 1, -1 }
875 #define TIO(k, i) { k, NULL, NULL, 0, i }
876 static struct {
877 enum key key;
878 char *cap;
879 const char *str;
880 size_t len;
881 int tio;
882 } keys[] = {
883 KEY(ALT_ENTER, "\033\n"),
884 KEY(BACKSPACE, "\177"),
885 KEY(BACKSPACE, "\b"),
886 KEY(CTRL_A, "\001"),
887 TIO(CTRL_C, VINTR),
888 KEY(CTRL_E, "\005"),
889 KEY(CTRL_K, "\013"),
890 KEY(CTRL_L, "\014"),
891 KEY(CTRL_O, "\017"),
892 KEY(CTRL_U, "\025"),
893 KEY(CTRL_W, "\027"),
894 KEY(CTRL_W, "\033\177"),
895 KEY(CTRL_W, "\033\b"),
896 TIO(CTRL_Z, VSUSP),
897 CAP(DEL, "kdch1"),
898 KEY(DEL, "\004"),
899 CAP(END, "kend"),
900 KEY(END, "\033>"),
901 KEY(ENTER, "\n"),
902 CAP(HOME, "khome"),
903 KEY(HOME, "\033<"),
904 CAP(LEFT, "kcub1"),
905 KEY(LEFT, "\002"),
906 KEY(LEFT, "\033OD"),
907 CAP(LINE_DOWN, "kcud1"),
908 KEY(LINE_DOWN, "\016"),
909 KEY(LINE_DOWN, "\033OB"),
910 CAP(LINE_UP, "kcuu1"),
911 KEY(LINE_UP, "\020"),
912 KEY(LINE_UP, "\033OA"),
913 CAP(PAGE_DOWN, "knp"),
914 KEY(PAGE_DOWN, "\026"),
915 KEY(PAGE_DOWN, "\033 "),
916 CAP(PAGE_UP, "kpp"),
917 KEY(PAGE_UP, "\033v"),
918 CAP(RIGHT, "kcuf1"),
919 KEY(RIGHT, "\006"),
920 KEY(RIGHT, "\033OC"),
921 KEY(UNKNOWN, NULL),
922 };
923 static unsigned char buf[8];
924 size_t len;
925 int c, i;
926
927 memset(buf, 0, sizeof(buf));
928 *key = (const char *)buf;
929 len = 0;
930
931 /*
932 * Allow SIGWINCH on the first read. If the signal is received, return
933 * CTRL_L which will trigger a resize.
934 */
935 toggle_sigwinch(1);
936 buf[len++] = tty_getc();
937 toggle_sigwinch(0);
938 if (gotsigwinch) {
939 gotsigwinch = 0;
940 return CTRL_L;
941 }
942
943 for (;;) {
944 for (i = 0; keys[i].key != UNKNOWN; i++) {
945 if (keys[i].tio >= 0) {
946 if (len == 1 &&
947 tio.c_cc[keys[i].tio] == *buf &&
948 tio.c_cc[keys[i].tio] != _POSIX_VDISABLE)
949 return keys[i].key;
950 continue;
951 }
952
953 if (keys[i].str == NULL) {
954 keys[i].str = tty_getcap(keys[i].cap);
955 keys[i].len = strlen(keys[i].str);
956 }
957 if (strncmp(keys[i].str, *key, len) != 0)
958 continue;
959
960 if (len == keys[i].len)
961 return keys[i].key;
962
963 /* Partial match found, continue reading. */
964 break;
965 }
966 if (keys[i].key == UNKNOWN)
967 break;
968
969 if (len == sizeof(buf) - 1)
970 break;
971 buf[len++] = tty_getc();
972 }
973
974 if (len > 1 && buf[0] == '\033' && (buf[1] == '[' || buf[1] == 'O')) {
975 /*
976 * An escape sequence which is not a supported key is being
977 * read. Discard the rest of the sequence.
978 */
979 c = buf[len - 1];
980 while (c < '@' || c > '~')
981 c = tty_getc();
982
983 return UNKNOWN;
984 }
985
986 if (!isu8start(buf[0])) {
987 if (isprint(buf[0]))
988 return PRINTABLE;
989
990 return UNKNOWN;
991 }
992
993 /*
994 * Ensure a whole Unicode character is read. The number of MSBs in the
995 * first octet of a Unicode character is equal to the number of octets
996 * the character consists of, followed by a zero. Therefore, as long as
997 * the MSB is not zero there is still bytes left to read.
998 */
999 for (;;) {
1000 if (((buf[0] << len) & 0x80) == 0)
1001 break;
1002 if (len == sizeof(buf) - 1)
1003 return UNKNOWN;
1004
1005 buf[len++] = tty_getc();
1006 }
1007
1008 return PRINTABLE;
1009 }
1010
1011 int
tty_getc(void)1012 tty_getc(void)
1013 {
1014 int c;
1015
1016 if ((c = getc(tty_in)) == ERR && !gotsigwinch)
1017 err(1, "getc");
1018
1019 return c;
1020 }
1021
1022 const char *
tty_getcap(char * cap)1023 tty_getcap(char *cap)
1024 {
1025 char *str;
1026
1027 str = tigetstr(cap);
1028 if (str == (char *)(-1) || str == NULL)
1029 return "";
1030
1031 return str;
1032 }
1033
1034 const char *
tty_parm1(char * cap,int a)1035 tty_parm1(char *cap, int a)
1036 {
1037 return tparm(cap, a, 0, 0, 0, 0, 0, 0, 0, 0);
1038 }
1039
1040 void
delete_between(char * string,size_t length,size_t start,size_t end)1041 delete_between(char *string, size_t length, size_t start, size_t end)
1042 {
1043 memmove(string + start, string + end, length - end + 1);
1044 }
1045
1046 int
isu8cont(unsigned char c)1047 isu8cont(unsigned char c)
1048 {
1049 return MB_CUR_MAX > 1 && (c & (0x80 | 0x40)) == 0x80;
1050 }
1051
1052 int
isu8start(unsigned char c)1053 isu8start(unsigned char c)
1054 {
1055 return MB_CUR_MAX > 1 && (c & (0x80 | 0x40)) == (0x80 | 0x40);
1056 }
1057
1058 int
isword(const char * s)1059 isword(const char *s)
1060 {
1061 wchar_t wc;
1062
1063 if (xmbtowc(&wc, s) == 0)
1064 return 0;
1065
1066 return iswalnum(wc) || wc == L'_';
1067 }
1068
1069 int
xmbtowc(wchar_t * wc,const char * s)1070 xmbtowc(wchar_t *wc, const char *s)
1071 {
1072 int n;
1073
1074 n = mbtowc(wc, s, MB_CUR_MAX);
1075 if (n == -1) {
1076 /* Assign in order to suppress ignored return value warning. */
1077 n = mbtowc(NULL, NULL, MB_CUR_MAX);
1078 return 0;
1079 }
1080
1081 return n;
1082 }
1083