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