xref: /openbsd-src/usr.bin/tmux/status.c (revision d59bb9942320b767f2a19aaa7690c8c6e30b724c)
1 /* $OpenBSD: status.c,v 1.162 2017/02/09 12:09:33 nicm Exp $ */
2 
3 /*
4  * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/types.h>
20 #include <sys/time.h>
21 
22 #include <errno.h>
23 #include <limits.h>
24 #include <stdarg.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <time.h>
28 #include <unistd.h>
29 
30 #include "tmux.h"
31 
32 static char	*status_redraw_get_left(struct client *, time_t,
33 		     struct grid_cell *, size_t *);
34 static char	*status_redraw_get_right(struct client *, time_t,
35 		     struct grid_cell *, size_t *);
36 static char	*status_print(struct client *, struct winlink *, time_t,
37 		     struct grid_cell *);
38 static char	*status_replace(struct client *, struct winlink *, const char *,
39 		     time_t);
40 static void	 status_message_callback(int, short, void *);
41 static void	 status_timer_callback(int, short, void *);
42 
43 static char	*status_prompt_find_history_file(void);
44 static const char *status_prompt_up_history(u_int *);
45 static const char *status_prompt_down_history(u_int *);
46 static void	 status_prompt_add_history(const char *);
47 
48 static const char **status_prompt_complete_list(u_int *, const char *);
49 static char	*status_prompt_complete_prefix(const char **, u_int);
50 static char	*status_prompt_complete(struct session *, const char *);
51 
52 /* Status prompt history. */
53 #define PROMPT_HISTORY 100
54 static char	**status_prompt_hlist;
55 static u_int	  status_prompt_hsize;
56 
57 /* Find the history file to load/save from/to. */
58 static char *
59 status_prompt_find_history_file(void)
60 {
61 	const char	*home, *history_file;
62 	char		*path;
63 
64 	history_file = options_get_string(global_options, "history-file");
65 	if (*history_file == '\0')
66 		return (NULL);
67 	if (*history_file == '/')
68 		return (xstrdup(history_file));
69 
70 	if (history_file[0] != '~' || history_file[1] != '/')
71 		return (NULL);
72 	if ((home = find_home()) == NULL)
73 		return (NULL);
74 	xasprintf(&path, "%s%s", home, history_file + 1);
75 	return (path);
76 }
77 
78 /* Load status prompt history from file. */
79 void
80 status_prompt_load_history(void)
81 {
82 	FILE	*f;
83 	char	*history_file, *line, *tmp;
84 	size_t	 length;
85 
86 	if ((history_file = status_prompt_find_history_file()) == NULL)
87 		return;
88 	log_debug("loading history from %s", history_file);
89 
90 	f = fopen(history_file, "r");
91 	if (f == NULL) {
92 		log_debug("%s: %s", history_file, strerror(errno));
93 		free(history_file);
94 		return;
95 	}
96 	free(history_file);
97 
98 	for (;;) {
99 		if ((line = fgetln(f, &length)) == NULL)
100 			break;
101 
102 		if (length > 0) {
103 			if (line[length - 1] == '\n') {
104 				line[length - 1] = '\0';
105 				status_prompt_add_history(line);
106 			} else {
107 				tmp = xmalloc(length + 1);
108 				memcpy(tmp, line, length);
109 				tmp[length] = '\0';
110 				status_prompt_add_history(tmp);
111 				free(tmp);
112 			}
113 		}
114 	}
115 	fclose(f);
116 }
117 
118 /* Save status prompt history to file. */
119 void
120 status_prompt_save_history(void)
121 {
122 	FILE	*f;
123 	u_int	 i;
124 	char	*history_file;
125 
126 	if ((history_file = status_prompt_find_history_file()) == NULL)
127 		return;
128 	log_debug("saving history to %s", history_file);
129 
130 	f = fopen(history_file, "w");
131 	if (f == NULL) {
132 		log_debug("%s: %s", history_file, strerror(errno));
133 		free(history_file);
134 		return;
135 	}
136 	free(history_file);
137 
138 	for (i = 0; i < status_prompt_hsize; i++) {
139 		fputs(status_prompt_hlist[i], f);
140 		fputc('\n', f);
141 	}
142 	fclose(f);
143 
144 }
145 
146 /* Status timer callback. */
147 static void
148 status_timer_callback(__unused int fd, __unused short events, void *arg)
149 {
150 	struct client	*c = arg;
151 	struct session	*s = c->session;
152 	struct timeval	 tv;
153 
154 	evtimer_del(&c->status_timer);
155 
156 	if (s == NULL)
157 		return;
158 
159 	if (c->message_string == NULL && c->prompt_string == NULL)
160 		c->flags |= CLIENT_STATUS;
161 
162 	timerclear(&tv);
163 	tv.tv_sec = options_get_number(s->options, "status-interval");
164 
165 	if (tv.tv_sec != 0)
166 		evtimer_add(&c->status_timer, &tv);
167 	log_debug("client %p, status interval %d", c, (int)tv.tv_sec);
168 }
169 
170 /* Start status timer for client. */
171 void
172 status_timer_start(struct client *c)
173 {
174 	struct session	*s = c->session;
175 
176 	if (event_initialized(&c->status_timer))
177 		evtimer_del(&c->status_timer);
178 	else
179 		evtimer_set(&c->status_timer, status_timer_callback, c);
180 
181 	if (s != NULL && options_get_number(s->options, "status"))
182 		status_timer_callback(-1, 0, c);
183 }
184 
185 /* Start status timer for all clients. */
186 void
187 status_timer_start_all(void)
188 {
189 	struct client	*c;
190 
191 	TAILQ_FOREACH(c, &clients, entry)
192 		status_timer_start(c);
193 }
194 
195 /* Update status cache. */
196 void
197 status_update_saved(struct session *s)
198 {
199 	if (!options_get_number(s->options, "status"))
200 		s->statusat = -1;
201 	else if (options_get_number(s->options, "status-position") == 0)
202 		s->statusat = 0;
203 	else
204 		s->statusat = 1;
205 }
206 
207 /* Get screen line of status line. -1 means off. */
208 int
209 status_at_line(struct client *c)
210 {
211 	struct session	*s = c->session;
212 
213 	if (s->statusat != 1)
214 		return (s->statusat);
215 	return (c->tty.sy - 1);
216 }
217 
218 /* Retrieve options for left string. */
219 static char *
220 status_redraw_get_left(struct client *c, time_t t, struct grid_cell *gc,
221     size_t *size)
222 {
223 	struct session	*s = c->session;
224 	const char	*template;
225 	char		*left;
226 	size_t		 leftlen;
227 
228 	style_apply_update(gc, s->options, "status-left-style");
229 
230 	template = options_get_string(s->options, "status-left");
231 	left = status_replace(c, NULL, template, t);
232 
233 	*size = options_get_number(s->options, "status-left-length");
234 	leftlen = screen_write_cstrlen("%s", left);
235 	if (leftlen < *size)
236 		*size = leftlen;
237 	return (left);
238 }
239 
240 /* Retrieve options for right string. */
241 static char *
242 status_redraw_get_right(struct client *c, time_t t, struct grid_cell *gc,
243     size_t *size)
244 {
245 	struct session	*s = c->session;
246 	const char	*template;
247 	char		*right;
248 	size_t		 rightlen;
249 
250 	style_apply_update(gc, s->options, "status-right-style");
251 
252 	template = options_get_string(s->options, "status-right");
253 	right = status_replace(c, NULL, template, t);
254 
255 	*size = options_get_number(s->options, "status-right-length");
256 	rightlen = screen_write_cstrlen("%s", right);
257 	if (rightlen < *size)
258 		*size = rightlen;
259 	return (right);
260 }
261 
262 /* Get window at window list position. */
263 struct window *
264 status_get_window_at(struct client *c, u_int x)
265 {
266 	struct session	*s = c->session;
267 	struct winlink	*wl;
268 	struct options	*oo;
269 	const char	*sep;
270 	size_t		 seplen;
271 
272 	x += c->wlmouse;
273 	RB_FOREACH(wl, winlinks, &s->windows) {
274 		oo = wl->window->options;
275 
276 		sep = options_get_string(oo, "window-status-separator");
277 		seplen = screen_write_cstrlen("%s", sep);
278 
279 		if (x < wl->status_width)
280 			return (wl->window);
281 		x -= wl->status_width + seplen;
282 	}
283 	return (NULL);
284 }
285 
286 /* Draw status for client on the last lines of given context. */
287 int
288 status_redraw(struct client *c)
289 {
290 	struct screen_write_ctx	 ctx;
291 	struct session		*s = c->session;
292 	struct winlink		*wl;
293 	struct screen		 old_status, window_list;
294 	struct grid_cell	 stdgc, lgc, rgc, gc;
295 	struct options		*oo;
296 	time_t			 t;
297 	char			*left, *right;
298 	const char		*sep;
299 	u_int			 offset, needed;
300 	u_int			 wlstart, wlwidth, wlavailable, wloffset, wlsize;
301 	size_t			 llen, rlen, seplen;
302 	int			 larrow, rarrow;
303 
304 	/* No status line? */
305 	if (c->tty.sy == 0 || !options_get_number(s->options, "status"))
306 		return (1);
307 	left = right = NULL;
308 	larrow = rarrow = 0;
309 
310 	/* Store current time. */
311 	t = time(NULL);
312 
313 	/* Set up default colour. */
314 	style_apply(&stdgc, s->options, "status-style");
315 
316 	/* Create the target screen. */
317 	memcpy(&old_status, &c->status, sizeof old_status);
318 	screen_init(&c->status, c->tty.sx, 1, 0);
319 	screen_write_start(&ctx, NULL, &c->status);
320 	for (offset = 0; offset < c->tty.sx; offset++)
321 		screen_write_putc(&ctx, &stdgc, ' ');
322 	screen_write_stop(&ctx);
323 
324 	/* If the height is one line, blank status line. */
325 	if (c->tty.sy <= 1)
326 		goto out;
327 
328 	/* Work out left and right strings. */
329 	memcpy(&lgc, &stdgc, sizeof lgc);
330 	left = status_redraw_get_left(c, t, &lgc, &llen);
331 	memcpy(&rgc, &stdgc, sizeof rgc);
332 	right = status_redraw_get_right(c, t, &rgc, &rlen);
333 
334 	/*
335 	 * Figure out how much space we have for the window list. If there
336 	 * isn't enough space, just show a blank status line.
337 	 */
338 	needed = 0;
339 	if (llen != 0)
340 		needed += llen;
341 	if (rlen != 0)
342 		needed += rlen;
343 	if (c->tty.sx == 0 || c->tty.sx <= needed)
344 		goto out;
345 	wlavailable = c->tty.sx - needed;
346 
347 	/* Calculate the total size needed for the window list. */
348 	wlstart = wloffset = wlwidth = 0;
349 	RB_FOREACH(wl, winlinks, &s->windows) {
350 		free(wl->status_text);
351 		memcpy(&wl->status_cell, &stdgc, sizeof wl->status_cell);
352 		wl->status_text = status_print(c, wl, t, &wl->status_cell);
353 		wl->status_width = screen_write_cstrlen("%s", wl->status_text);
354 
355 		if (wl == s->curw)
356 			wloffset = wlwidth;
357 
358 		oo = wl->window->options;
359 		sep = options_get_string(oo, "window-status-separator");
360 		seplen = screen_write_cstrlen("%s", sep);
361 		wlwidth += wl->status_width + seplen;
362 	}
363 
364 	/* Create a new screen for the window list. */
365 	screen_init(&window_list, wlwidth, 1, 0);
366 
367 	/* And draw the window list into it. */
368 	screen_write_start(&ctx, NULL, &window_list);
369 	RB_FOREACH(wl, winlinks, &s->windows) {
370 		screen_write_cnputs(&ctx, -1, &wl->status_cell, "%s",
371 		    wl->status_text);
372 
373 		oo = wl->window->options;
374 		sep = options_get_string(oo, "window-status-separator");
375 		screen_write_cnputs(&ctx, -1, &stdgc, "%s", sep);
376 	}
377 	screen_write_stop(&ctx);
378 
379 	/* If there is enough space for the total width, skip to draw now. */
380 	if (wlwidth <= wlavailable)
381 		goto draw;
382 
383 	/* Find size of current window text. */
384 	wlsize = s->curw->status_width;
385 
386 	/*
387 	 * If the current window is already on screen, good to draw from the
388 	 * start and just leave off the end.
389 	 */
390 	if (wloffset + wlsize < wlavailable) {
391 		if (wlavailable > 0) {
392 			rarrow = 1;
393 			wlavailable--;
394 		}
395 		wlwidth = wlavailable;
396 	} else {
397 		/*
398 		 * Work out how many characters we need to omit from the
399 		 * start. There are wlavailable characters to fill, and
400 		 * wloffset + wlsize must be the last. So, the start character
401 		 * is wloffset + wlsize - wlavailable.
402 		 */
403 		if (wlavailable > 0) {
404 			larrow = 1;
405 			wlavailable--;
406 		}
407 
408 		wlstart = wloffset + wlsize - wlavailable;
409 		if (wlavailable > 0 && wlwidth > wlstart + wlavailable + 1) {
410 			rarrow = 1;
411 			wlstart++;
412 			wlavailable--;
413 		}
414 		wlwidth = wlavailable;
415 	}
416 
417 	/* Bail if anything is now too small too. */
418 	if (wlwidth == 0 || wlavailable == 0) {
419 		screen_free(&window_list);
420 		goto out;
421 	}
422 
423 	/*
424 	 * Now the start position is known, work out the state of the left and
425 	 * right arrows.
426 	 */
427 	offset = 0;
428 	RB_FOREACH(wl, winlinks, &s->windows) {
429 		if (wl->flags & WINLINK_ALERTFLAGS &&
430 		    larrow == 1 && offset < wlstart)
431 			larrow = -1;
432 
433 		offset += wl->status_width;
434 
435 		if (wl->flags & WINLINK_ALERTFLAGS &&
436 		    rarrow == 1 && offset > wlstart + wlwidth)
437 			rarrow = -1;
438 	}
439 
440 draw:
441 	/* Begin drawing. */
442 	screen_write_start(&ctx, NULL, &c->status);
443 
444 	/* Draw the left string and arrow. */
445 	screen_write_cursormove(&ctx, 0, 0);
446 	if (llen != 0)
447 		screen_write_cnputs(&ctx, llen, &lgc, "%s", left);
448 	if (larrow != 0) {
449 		memcpy(&gc, &stdgc, sizeof gc);
450 		if (larrow == -1)
451 			gc.attr ^= GRID_ATTR_REVERSE;
452 		screen_write_putc(&ctx, &gc, '<');
453 	}
454 
455 	/* Draw the right string and arrow. */
456 	if (rarrow != 0) {
457 		screen_write_cursormove(&ctx, c->tty.sx - rlen - 1, 0);
458 		memcpy(&gc, &stdgc, sizeof gc);
459 		if (rarrow == -1)
460 			gc.attr ^= GRID_ATTR_REVERSE;
461 		screen_write_putc(&ctx, &gc, '>');
462 	} else
463 		screen_write_cursormove(&ctx, c->tty.sx - rlen, 0);
464 	if (rlen != 0)
465 		screen_write_cnputs(&ctx, rlen, &rgc, "%s", right);
466 
467 	/* Figure out the offset for the window list. */
468 	if (llen != 0)
469 		wloffset = llen;
470 	else
471 		wloffset = 0;
472 	if (wlwidth < wlavailable) {
473 		switch (options_get_number(s->options, "status-justify")) {
474 		case 1:	/* centred */
475 			wloffset += (wlavailable - wlwidth) / 2;
476 			break;
477 		case 2:	/* right */
478 			wloffset += (wlavailable - wlwidth);
479 			break;
480 		}
481 	}
482 	if (larrow != 0)
483 		wloffset++;
484 
485 	/* Copy the window list. */
486 	c->wlmouse = -wloffset + wlstart;
487 	screen_write_cursormove(&ctx, wloffset, 0);
488 	screen_write_copy(&ctx, &window_list, wlstart, 0, wlwidth, 1, NULL,
489 	    NULL);
490 	screen_free(&window_list);
491 
492 	screen_write_stop(&ctx);
493 
494 out:
495 	free(left);
496 	free(right);
497 
498 	if (grid_compare(c->status.grid, old_status.grid) == 0) {
499 		screen_free(&old_status);
500 		return (0);
501 	}
502 	screen_free(&old_status);
503 	return (1);
504 }
505 
506 /* Replace special sequences in fmt. */
507 static char *
508 status_replace(struct client *c, struct winlink *wl, const char *fmt, time_t t)
509 {
510 	struct format_tree	*ft;
511 	char			*expanded;
512 	u_int			 tag;
513 
514 	if (fmt == NULL)
515 		return (xstrdup(""));
516 
517 	if (wl != NULL)
518 		tag = FORMAT_WINDOW|wl->window->id;
519 	else
520 		tag = FORMAT_NONE;
521 	if (c->flags & CLIENT_STATUSFORCE)
522 		ft = format_create(NULL, tag, FORMAT_STATUS|FORMAT_FORCE);
523 	else
524 		ft = format_create(NULL, tag, FORMAT_STATUS);
525 	format_defaults(ft, c, NULL, wl, NULL);
526 
527 	expanded = format_expand_time(ft, fmt, t);
528 
529 	format_free(ft);
530 	return (expanded);
531 }
532 
533 /* Return winlink status line entry and adjust gc as necessary. */
534 static char *
535 status_print(struct client *c, struct winlink *wl, time_t t,
536     struct grid_cell *gc)
537 {
538 	struct options	*oo = wl->window->options;
539 	struct session	*s = c->session;
540 	const char	*fmt;
541 	char   		*text;
542 
543 	style_apply_update(gc, oo, "window-status-style");
544 	fmt = options_get_string(oo, "window-status-format");
545 	if (wl == s->curw) {
546 		style_apply_update(gc, oo, "window-status-current-style");
547 		fmt = options_get_string(oo, "window-status-current-format");
548 	}
549 	if (wl == TAILQ_FIRST(&s->lastw))
550 		style_apply_update(gc, oo, "window-status-last-style");
551 
552 	if (wl->flags & WINLINK_BELL)
553 		style_apply_update(gc, oo, "window-status-bell-style");
554 	else if (wl->flags & (WINLINK_ACTIVITY|WINLINK_SILENCE))
555 		style_apply_update(gc, oo, "window-status-activity-style");
556 
557 	text = status_replace(c, wl, fmt, t);
558 	return (text);
559 }
560 
561 /* Set a status line message. */
562 void
563 status_message_set(struct client *c, const char *fmt, ...)
564 {
565 	struct timeval	tv;
566 	va_list		ap;
567 	int		delay;
568 
569 	status_message_clear(c);
570 
571 	va_start(ap, fmt);
572 	xvasprintf(&c->message_string, fmt, ap);
573 	va_end(ap);
574 
575 	server_client_add_message(c, "%s", c->message_string);
576 
577 	delay = options_get_number(c->session->options, "display-time");
578 	if (delay > 0) {
579 		tv.tv_sec = delay / 1000;
580 		tv.tv_usec = (delay % 1000) * 1000L;
581 
582 		if (event_initialized(&c->message_timer))
583 			evtimer_del(&c->message_timer);
584 		evtimer_set(&c->message_timer, status_message_callback, c);
585 		evtimer_add(&c->message_timer, &tv);
586 	}
587 
588 	c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
589 	c->flags |= CLIENT_STATUS;
590 }
591 
592 /* Clear status line message. */
593 void
594 status_message_clear(struct client *c)
595 {
596 	if (c->message_string == NULL)
597 		return;
598 
599 	free(c->message_string);
600 	c->message_string = NULL;
601 
602 	if (c->prompt_string == NULL)
603 		c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
604 	c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
605 
606 	screen_reinit(&c->status);
607 }
608 
609 /* Clear status line message after timer expires. */
610 static void
611 status_message_callback(__unused int fd, __unused short event, void *data)
612 {
613 	struct client	*c = data;
614 
615 	status_message_clear(c);
616 }
617 
618 /* Draw client message on status line of present else on last line. */
619 int
620 status_message_redraw(struct client *c)
621 {
622 	struct screen_write_ctx		ctx;
623 	struct session		       *s = c->session;
624 	struct screen		        old_status;
625 	size_t			        len;
626 	struct grid_cell		gc;
627 
628 	if (c->tty.sx == 0 || c->tty.sy == 0)
629 		return (0);
630 	memcpy(&old_status, &c->status, sizeof old_status);
631 	screen_init(&c->status, c->tty.sx, 1, 0);
632 
633 	len = screen_write_strlen("%s", c->message_string);
634 	if (len > c->tty.sx)
635 		len = c->tty.sx;
636 
637 	style_apply(&gc, s->options, "message-style");
638 
639 	screen_write_start(&ctx, NULL, &c->status);
640 
641 	screen_write_cursormove(&ctx, 0, 0);
642 	screen_write_nputs(&ctx, len, &gc, "%s", c->message_string);
643 	for (; len < c->tty.sx; len++)
644 		screen_write_putc(&ctx, &gc, ' ');
645 
646 	screen_write_stop(&ctx);
647 
648 	if (grid_compare(c->status.grid, old_status.grid) == 0) {
649 		screen_free(&old_status);
650 		return (0);
651 	}
652 	screen_free(&old_status);
653 	return (1);
654 }
655 
656 /* Enable status line prompt. */
657 void
658 status_prompt_set(struct client *c, const char *msg, const char *input,
659     int (*callbackfn)(void *, const char *, int), void (*freefn)(void *),
660     void *data, int flags)
661 {
662 	struct format_tree	*ft;
663 	time_t			 t;
664 	char			*tmp;
665 
666 	ft = format_create(NULL, FORMAT_NONE, 0);
667 	format_defaults(ft, c, NULL, NULL, NULL);
668 
669 	t = time(NULL);
670 	tmp = format_expand_time(ft, input, t);
671 
672 	status_message_clear(c);
673 	status_prompt_clear(c);
674 
675 	c->prompt_string = format_expand_time(ft, msg, t);
676 
677 	c->prompt_buffer = utf8_fromcstr(tmp);
678 	c->prompt_index = utf8_strlen(c->prompt_buffer);
679 
680 	c->prompt_callbackfn = callbackfn;
681 	c->prompt_freefn = freefn;
682 	c->prompt_data = data;
683 
684 	c->prompt_hindex = 0;
685 
686 	c->prompt_flags = flags;
687 	c->prompt_mode = PROMPT_ENTRY;
688 
689 	if (~flags & PROMPT_INCREMENTAL)
690 		c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
691 	c->flags |= CLIENT_STATUS;
692 
693 	free(tmp);
694 	format_free(ft);
695 }
696 
697 /* Remove status line prompt. */
698 void
699 status_prompt_clear(struct client *c)
700 {
701 	if (c->prompt_string == NULL)
702 		return;
703 
704 	if (c->prompt_freefn != NULL && c->prompt_data != NULL)
705 		c->prompt_freefn(c->prompt_data);
706 
707 	free(c->prompt_string);
708 	c->prompt_string = NULL;
709 
710 	free(c->prompt_buffer);
711 	c->prompt_buffer = NULL;
712 
713 	c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
714 	c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
715 
716 	screen_reinit(&c->status);
717 }
718 
719 /* Update status line prompt with a new prompt string. */
720 void
721 status_prompt_update(struct client *c, const char *msg, const char *input)
722 {
723 	struct format_tree	*ft;
724 	time_t			 t;
725 	char			*tmp;
726 
727 	ft = format_create(NULL, FORMAT_NONE, 0);
728 	format_defaults(ft, c, NULL, NULL, NULL);
729 
730 	t = time(NULL);
731 	tmp = format_expand_time(ft, input, t);
732 
733 	free(c->prompt_string);
734 	c->prompt_string = format_expand_time(ft, msg, t);
735 
736 	free(c->prompt_buffer);
737 	c->prompt_buffer = utf8_fromcstr(tmp);
738 	c->prompt_index = utf8_strlen(c->prompt_buffer);
739 
740 	c->prompt_hindex = 0;
741 
742 	c->flags |= CLIENT_STATUS;
743 
744 	free(tmp);
745 	format_free(ft);
746 }
747 
748 /* Draw client prompt on status line of present else on last line. */
749 int
750 status_prompt_redraw(struct client *c)
751 {
752 	struct screen_write_ctx	 ctx;
753 	struct session		*s = c->session;
754 	struct screen		 old_status;
755 	u_int			 i, offset, left, start, pcursor, pwidth, width;
756 	struct grid_cell	 gc, cursorgc;
757 
758 	if (c->tty.sx == 0 || c->tty.sy == 0)
759 		return (0);
760 	memcpy(&old_status, &c->status, sizeof old_status);
761 	screen_init(&c->status, c->tty.sx, 1, 0);
762 
763 	if (c->prompt_mode == PROMPT_COMMAND)
764 		style_apply(&gc, s->options, "message-command-style");
765 	else
766 		style_apply(&gc, s->options, "message-style");
767 
768 	memcpy(&cursorgc, &gc, sizeof cursorgc);
769 	cursorgc.attr ^= GRID_ATTR_REVERSE;
770 
771 	start = screen_write_strlen("%s", c->prompt_string);
772 	if (start > c->tty.sx)
773 		start = c->tty.sx;
774 
775 	screen_write_start(&ctx, NULL, &c->status);
776 	screen_write_cursormove(&ctx, 0, 0);
777 	screen_write_nputs(&ctx, start, &gc, "%s", c->prompt_string);
778 	while (c->status.cx < screen_size_x(&c->status))
779 		screen_write_putc(&ctx, &gc, ' ');
780 	screen_write_cursormove(&ctx, start, 0);
781 
782 	left = c->tty.sx - start;
783 	if (left == 0)
784 		goto finished;
785 
786 	pcursor = utf8_strwidth(c->prompt_buffer, c->prompt_index);
787 	pwidth = utf8_strwidth(c->prompt_buffer, -1);
788 	if (pcursor >= left) {
789 		/*
790 		 * The cursor would be outside the screen so start drawing
791 		 * with it on the right.
792 		 */
793 		offset = (pcursor - left) + 1;
794 		pwidth = left;
795 	} else
796 		offset = 0;
797 	if (pwidth > left)
798 		pwidth = left;
799 
800 	width = 0;
801 	for (i = 0; c->prompt_buffer[i].size != 0; i++) {
802 		if (width < offset) {
803 			width += c->prompt_buffer[i].width;
804 			continue;
805 		}
806 		if (width >= offset + pwidth)
807 			break;
808 		width += c->prompt_buffer[i].width;
809 		if (width > offset + pwidth)
810 			break;
811 
812 		if (i != c->prompt_index) {
813 			utf8_copy(&gc.data, &c->prompt_buffer[i]);
814 			screen_write_cell(&ctx, &gc);
815 		} else {
816 			utf8_copy(&cursorgc.data, &c->prompt_buffer[i]);
817 			screen_write_cell(&ctx, &cursorgc);
818 		}
819 	}
820 	if (c->status.cx < screen_size_x(&c->status) && c->prompt_index >= i)
821 		screen_write_putc(&ctx, &cursorgc, ' ');
822 
823 finished:
824 	screen_write_stop(&ctx);
825 
826 	if (grid_compare(c->status.grid, old_status.grid) == 0) {
827 		screen_free(&old_status);
828 		return (0);
829 	}
830 	screen_free(&old_status);
831 	return (1);
832 }
833 
834 /* Is this a separator? */
835 static int
836 status_prompt_in_list(const char *ws, const struct utf8_data *ud)
837 {
838 	if (ud->size != 1 || ud->width != 1)
839 		return (0);
840 	return (strchr(ws, *ud->data) != NULL);
841 }
842 
843 /* Is this a space? */
844 static int
845 status_prompt_space(const struct utf8_data *ud)
846 {
847 	if (ud->size != 1 || ud->width != 1)
848 		return (0);
849 	return (*ud->data == ' ');
850 }
851 
852 /*
853  * Translate key from emacs to vi. Return 0 to drop key, 1 to process the key
854  * as an emacs key; return 2 to append to the buffer.
855  */
856 static int
857 status_prompt_translate_key(struct client *c, key_code key, key_code *new_key)
858 {
859 	if (c->prompt_mode == PROMPT_ENTRY) {
860 		switch (key) {
861 		case '\003': /* C-c */
862 		case '\010': /* C-h */
863 		case '\011': /* Tab */
864 		case '\025': /* C-u */
865 		case '\027': /* C-w */
866 		case '\n':
867 		case '\r':
868 		case KEYC_BSPACE:
869 		case KEYC_DC:
870 		case KEYC_DOWN:
871 		case KEYC_END:
872 		case KEYC_HOME:
873 		case KEYC_LEFT:
874 		case KEYC_RIGHT:
875 		case KEYC_UP:
876 			*new_key = key;
877 			return (1);
878 		case '\033': /* Escape */
879 			c->prompt_mode = PROMPT_COMMAND;
880 			c->flags |= CLIENT_STATUS;
881 			return (0);
882 		}
883 		*new_key = key;
884 		return (2);
885 	}
886 
887 	switch (key) {
888 	case 'A':
889 	case 'I':
890 	case 'C':
891 	case 's':
892 	case 'a':
893 		c->prompt_mode = PROMPT_ENTRY;
894 		c->flags |= CLIENT_STATUS;
895 		break; /* switch mode and... */
896 	case 'S':
897 		c->prompt_mode = PROMPT_ENTRY;
898 		c->flags |= CLIENT_STATUS;
899 		*new_key = '\025'; /* C-u */
900 		return (1);
901 	case 'i':
902 	case '\033': /* Escape */
903 		c->prompt_mode = PROMPT_ENTRY;
904 		c->flags |= CLIENT_STATUS;
905 		return (0);
906 	}
907 
908 	switch (key) {
909 	case 'A':
910 	case '$':
911 		*new_key = KEYC_END;
912 		return (1);
913 	case 'I':
914 	case '0':
915 	case '^':
916 		*new_key = KEYC_HOME;
917 		return (1);
918 	case 'C':
919 	case 'D':
920 		*new_key = '\013'; /* C-k */
921 		return (1);
922 	case KEYC_BSPACE:
923 	case 'X':
924 		*new_key = KEYC_BSPACE;
925 		return (1);
926 	case 'b':
927 	case 'B':
928 		*new_key = 'b'|KEYC_ESCAPE;
929 		return (1);
930 	case 'd':
931 		*new_key = '\025';
932 		return (1);
933 	case 'e':
934 	case 'E':
935 	case 'w':
936 	case 'W':
937 		*new_key = 'f'|KEYC_ESCAPE;
938 		return (1);
939 	case 'p':
940 		*new_key = '\031'; /* C-y */
941 		return (1);
942 	case 's':
943 	case KEYC_DC:
944 	case 'x':
945 		*new_key = KEYC_DC;
946 		return (1);
947 	case KEYC_DOWN:
948 	case 'j':
949 		*new_key = KEYC_DOWN;
950 		return (1);
951 	case KEYC_LEFT:
952 	case 'h':
953 		*new_key = KEYC_LEFT;
954 		return (1);
955 	case 'a':
956 	case KEYC_RIGHT:
957 	case 'l':
958 		*new_key = KEYC_RIGHT;
959 		return (1);
960 	case KEYC_UP:
961 	case 'k':
962 		*new_key = KEYC_UP;
963 		return (1);
964 	case '\010' /* C-h */:
965 	case '\003' /* C-c */:
966 	case '\n':
967 	case '\r':
968 		return (1);
969 	}
970 	return (0);
971 }
972 
973 /* Handle keys in prompt. */
974 int
975 status_prompt_key(struct client *c, key_code key)
976 {
977 	struct options		*oo = c->session->options;
978 	struct paste_buffer	*pb;
979 	char			*s, *cp, word[64], prefix = '=';
980 	const char		*histstr, *bufdata, *ws = NULL;
981 	u_char			 ch;
982 	size_t			 size, n, off, idx, bufsize, used;
983 	struct utf8_data	 tmp, *first, *last, *ud;
984 	int			 keys;
985 
986 	size = utf8_strlen(c->prompt_buffer);
987 
988 	if (c->prompt_flags & PROMPT_NUMERIC) {
989 		if (key >= '0' && key <= '9')
990 			goto append_key;
991 		s = utf8_tocstr(c->prompt_buffer);
992 		c->prompt_callbackfn(c->prompt_data, s, 1);
993 		status_prompt_clear(c);
994 		free(s);
995 		return (1);
996 	}
997 
998 	keys = options_get_number(c->session->options, "status-keys");
999 	if (keys == MODEKEY_VI) {
1000 		switch (status_prompt_translate_key(c, key, &key)) {
1001 		case 1:
1002 			goto process_key;
1003 		case 2:
1004 			goto append_key;
1005 		default:
1006 			return (0);
1007 		}
1008 	}
1009 
1010 process_key:
1011 	switch (key) {
1012 	case KEYC_LEFT:
1013 	case '\002': /* C-b */
1014 		if (c->prompt_index > 0) {
1015 			c->prompt_index--;
1016 			break;
1017 		}
1018 		break;
1019 	case KEYC_RIGHT:
1020 	case '\006': /* C-f */
1021 		if (c->prompt_index < size) {
1022 			c->prompt_index++;
1023 			break;
1024 		}
1025 		break;
1026 	case KEYC_HOME:
1027 	case '\001': /* C-a */
1028 		if (c->prompt_index != 0) {
1029 			c->prompt_index = 0;
1030 			break;
1031 		}
1032 		break;
1033 	case KEYC_END:
1034 	case '\005': /* C-e */
1035 		if (c->prompt_index != size) {
1036 			c->prompt_index = size;
1037 			break;
1038 		}
1039 		break;
1040 	case '\011': /* Tab */
1041 		if (c->prompt_buffer[0].size == 0)
1042 			break;
1043 
1044 		idx = c->prompt_index;
1045 		if (idx != 0)
1046 			idx--;
1047 
1048 		/* Find the word we are in. */
1049 		first = &c->prompt_buffer[idx];
1050 		while (first > c->prompt_buffer && !status_prompt_space(first))
1051 			first--;
1052 		while (first->size != 0 && status_prompt_space(first))
1053 			first++;
1054 		last = &c->prompt_buffer[idx];
1055 		while (last->size != 0 && !status_prompt_space(last))
1056 			last++;
1057 		while (last > c->prompt_buffer && status_prompt_space(last))
1058 			last--;
1059 		if (last->size != 0)
1060 			last++;
1061 		if (last <= first)
1062 			break;
1063 
1064 		used = 0;
1065 		for (ud = first; ud < last; ud++) {
1066 			if (used + ud->size >= sizeof word)
1067 				break;
1068 			memcpy(word + used, ud->data, ud->size);
1069 			used += ud->size;
1070 		}
1071 		if (ud != last)
1072 			break;
1073 		word[used] = '\0';
1074 
1075 		/* And try to complete it. */
1076 		if ((s = status_prompt_complete(c->session, word)) == NULL)
1077 			break;
1078 
1079 		/* Trim out word. */
1080 		n = size - (last - c->prompt_buffer) + 1; /* with \0 */
1081 		memmove(first, last, n * sizeof *c->prompt_buffer);
1082 		size -= last - first;
1083 
1084 		/* Insert the new word. */
1085 		size += strlen(s);
1086 		off = first - c->prompt_buffer;
1087 		c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 1,
1088 		    sizeof *c->prompt_buffer);
1089 		first = c->prompt_buffer + off;
1090 		memmove(first + strlen(s), first, n * sizeof *c->prompt_buffer);
1091 		for (idx = 0; idx < strlen(s); idx++)
1092 			utf8_set(&first[idx], s[idx]);
1093 
1094 		c->prompt_index = (first - c->prompt_buffer) + strlen(s);
1095 		free(s);
1096 
1097 		goto changed;
1098 	case KEYC_BSPACE:
1099 	case '\010': /* C-h */
1100 		if (c->prompt_index != 0) {
1101 			if (c->prompt_index == size)
1102 				c->prompt_buffer[--c->prompt_index].size = 0;
1103 			else {
1104 				memmove(c->prompt_buffer + c->prompt_index - 1,
1105 				    c->prompt_buffer + c->prompt_index,
1106 				    (size + 1 - c->prompt_index) *
1107 				    sizeof *c->prompt_buffer);
1108 				c->prompt_index--;
1109 			}
1110 			goto changed;
1111 		}
1112 		break;
1113 	case KEYC_DC:
1114 	case '\004': /* C-d */
1115 		if (c->prompt_index != size) {
1116 			memmove(c->prompt_buffer + c->prompt_index,
1117 			    c->prompt_buffer + c->prompt_index + 1,
1118 			    (size + 1 - c->prompt_index) *
1119 			    sizeof *c->prompt_buffer);
1120 			goto changed;
1121 		}
1122 		break;
1123 	case '\025': /* C-u */
1124 		c->prompt_buffer[0].size = 0;
1125 		c->prompt_index = 0;
1126 		goto changed;
1127 	case '\013': /* C-k */
1128 		if (c->prompt_index < size) {
1129 			c->prompt_buffer[c->prompt_index].size = 0;
1130 			goto changed;
1131 		}
1132 		break;
1133 	case '\027': /* C-w */
1134 		ws = options_get_string(oo, "word-separators");
1135 		idx = c->prompt_index;
1136 
1137 		/* Find a non-separator. */
1138 		while (idx != 0) {
1139 			idx--;
1140 			if (!status_prompt_in_list(ws, &c->prompt_buffer[idx]))
1141 				break;
1142 		}
1143 
1144 		/* Find the separator at the beginning of the word. */
1145 		while (idx != 0) {
1146 			idx--;
1147 			if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) {
1148 				/* Go back to the word. */
1149 				idx++;
1150 				break;
1151 			}
1152 		}
1153 
1154 		memmove(c->prompt_buffer + idx,
1155 		    c->prompt_buffer + c->prompt_index,
1156 		    (size + 1 - c->prompt_index) *
1157 		    sizeof *c->prompt_buffer);
1158 		memset(c->prompt_buffer + size - (c->prompt_index - idx),
1159 		    '\0', (c->prompt_index - idx) * sizeof *c->prompt_buffer);
1160 		c->prompt_index = idx;
1161 
1162 		goto changed;
1163 	case 'f'|KEYC_ESCAPE:
1164 		ws = options_get_string(oo, "word-separators");
1165 
1166 		/* Find a word. */
1167 		while (c->prompt_index != size) {
1168 			idx = ++c->prompt_index;
1169 			if (!status_prompt_in_list(ws, &c->prompt_buffer[idx]))
1170 				break;
1171 		}
1172 
1173 		/* Find the separator at the end of the word. */
1174 		while (c->prompt_index != size) {
1175 			idx = ++c->prompt_index;
1176 			if (status_prompt_in_list(ws, &c->prompt_buffer[idx]))
1177 				break;
1178 		}
1179 
1180 		/* Back up to the end-of-word like vi. */
1181 		if (options_get_number(oo, "status-keys") == MODEKEY_VI &&
1182 		    c->prompt_index != 0)
1183 			c->prompt_index--;
1184 
1185 		goto changed;
1186 	case 'b'|KEYC_ESCAPE:
1187 		ws = options_get_string(oo, "word-separators");
1188 
1189 		/* Find a non-separator. */
1190 		while (c->prompt_index != 0) {
1191 			idx = --c->prompt_index;
1192 			if (!status_prompt_in_list(ws, &c->prompt_buffer[idx]))
1193 				break;
1194 		}
1195 
1196 		/* Find the separator at the beginning of the word. */
1197 		while (c->prompt_index != 0) {
1198 			idx = --c->prompt_index;
1199 			if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) {
1200 				/* Go back to the word. */
1201 				c->prompt_index++;
1202 				break;
1203 			}
1204 		}
1205 		goto changed;
1206 	case KEYC_UP:
1207 	case '\020': /* C-p */
1208 		histstr = status_prompt_up_history(&c->prompt_hindex);
1209 		if (histstr == NULL)
1210 			break;
1211 		free(c->prompt_buffer);
1212 		c->prompt_buffer = utf8_fromcstr(histstr);
1213 		c->prompt_index = utf8_strlen(c->prompt_buffer);
1214 		goto changed;
1215 	case KEYC_DOWN:
1216 	case '\016': /* C-n */
1217 		histstr = status_prompt_down_history(&c->prompt_hindex);
1218 		if (histstr == NULL)
1219 			break;
1220 		free(c->prompt_buffer);
1221 		c->prompt_buffer = utf8_fromcstr(histstr);
1222 		c->prompt_index = utf8_strlen(c->prompt_buffer);
1223 		goto changed;
1224 	case '\031': /* C-y */
1225 		if ((pb = paste_get_top(NULL)) == NULL)
1226 			break;
1227 		bufdata = paste_buffer_data(pb, &bufsize);
1228 		for (n = 0; n < bufsize; n++) {
1229 			ch = (u_char)bufdata[n];
1230 			if (ch < 32 || ch >= 127)
1231 				break;
1232 		}
1233 
1234 		c->prompt_buffer = xreallocarray(c->prompt_buffer, size + n + 1,
1235 		    sizeof *c->prompt_buffer);
1236 		if (c->prompt_index == size) {
1237 			for (idx = 0; idx < n; idx++) {
1238 				ud = &c->prompt_buffer[c->prompt_index + idx];
1239 				utf8_set(ud, bufdata[idx]);
1240 			}
1241 			c->prompt_index += n;
1242 			c->prompt_buffer[c->prompt_index].size = 0;
1243 		} else {
1244 			memmove(c->prompt_buffer + c->prompt_index + n,
1245 			    c->prompt_buffer + c->prompt_index,
1246 			    (size + 1 - c->prompt_index) *
1247 			    sizeof *c->prompt_buffer);
1248 			for (idx = 0; idx < n; idx++) {
1249 				ud = &c->prompt_buffer[c->prompt_index + idx];
1250 				utf8_set(ud, bufdata[idx]);
1251 			}
1252 			c->prompt_index += n;
1253 		}
1254 		goto changed;
1255 	case '\024': /* C-t */
1256 		idx = c->prompt_index;
1257 		if (idx < size)
1258 			idx++;
1259 		if (idx >= 2) {
1260 			utf8_copy(&tmp, &c->prompt_buffer[idx - 2]);
1261 			utf8_copy(&c->prompt_buffer[idx - 2],
1262 			    &c->prompt_buffer[idx - 1]);
1263 			utf8_copy(&c->prompt_buffer[idx - 1], &tmp);
1264 			c->prompt_index = idx;
1265 			goto changed;
1266 		}
1267 		break;
1268 	case '\r':
1269 	case '\n':
1270 		s = utf8_tocstr(c->prompt_buffer);
1271 		if (*s != '\0')
1272 			status_prompt_add_history(s);
1273 		if (c->prompt_callbackfn(c->prompt_data, s, 1) == 0)
1274 			status_prompt_clear(c);
1275 		free(s);
1276 		break;
1277 	case '\033': /* Escape */
1278 	case '\003': /* C-c */
1279 		if (c->prompt_callbackfn(c->prompt_data, NULL, 1) == 0)
1280 			status_prompt_clear(c);
1281 		break;
1282 	case '\022': /* C-r */
1283 		if (c->prompt_flags & PROMPT_INCREMENTAL) {
1284 			prefix = '-';
1285 			goto changed;
1286 		}
1287 		break;
1288 	case '\023': /* C-s */
1289 		if (c->prompt_flags & PROMPT_INCREMENTAL) {
1290 			prefix = '+';
1291 			goto changed;
1292 		}
1293 		break;
1294 	default:
1295 		goto append_key;
1296 	}
1297 
1298 	c->flags |= CLIENT_STATUS;
1299 	return (0);
1300 
1301 append_key:
1302 	if (key <= 0x1f || key >= KEYC_BASE)
1303 		return (0);
1304 	if (utf8_split(key, &tmp) != UTF8_DONE)
1305 		return (0);
1306 
1307 	c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 2,
1308 	    sizeof *c->prompt_buffer);
1309 
1310 	if (c->prompt_index == size) {
1311 		utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp);
1312 		c->prompt_index++;
1313 		c->prompt_buffer[c->prompt_index].size = 0;
1314 	} else {
1315 		memmove(c->prompt_buffer + c->prompt_index + 1,
1316 		    c->prompt_buffer + c->prompt_index,
1317 		    (size + 1 - c->prompt_index) *
1318 		    sizeof *c->prompt_buffer);
1319 		utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp);
1320 		c->prompt_index++;
1321 	}
1322 
1323 	if (c->prompt_flags & PROMPT_SINGLE) {
1324 		s = utf8_tocstr(c->prompt_buffer);
1325 		if (strlen(s) != 1)
1326 			status_prompt_clear(c);
1327 		else if (c->prompt_callbackfn(c->prompt_data, s, 1) == 0)
1328 			status_prompt_clear(c);
1329 		free(s);
1330 	}
1331 
1332 changed:
1333 	c->flags |= CLIENT_STATUS;
1334 	if (c->prompt_flags & PROMPT_INCREMENTAL) {
1335 		s = utf8_tocstr(c->prompt_buffer);
1336 		xasprintf(&cp, "%c%s", prefix, s);
1337 		c->prompt_callbackfn(c->prompt_data, cp, 0);
1338 		free(cp);
1339 		free(s);
1340 	}
1341 	return (0);
1342 }
1343 
1344 /* Get previous line from the history. */
1345 static const char *
1346 status_prompt_up_history(u_int *idx)
1347 {
1348 	/*
1349 	 * History runs from 0 to size - 1. Index is from 0 to size. Zero is
1350 	 * empty.
1351 	 */
1352 
1353 	if (status_prompt_hsize == 0 || *idx == status_prompt_hsize)
1354 		return (NULL);
1355 	(*idx)++;
1356 	return (status_prompt_hlist[status_prompt_hsize - *idx]);
1357 }
1358 
1359 /* Get next line from the history. */
1360 static const char *
1361 status_prompt_down_history(u_int *idx)
1362 {
1363 	if (status_prompt_hsize == 0 || *idx == 0)
1364 		return ("");
1365 	(*idx)--;
1366 	if (*idx == 0)
1367 		return ("");
1368 	return (status_prompt_hlist[status_prompt_hsize - *idx]);
1369 }
1370 
1371 /* Add line to the history. */
1372 static void
1373 status_prompt_add_history(const char *line)
1374 {
1375 	size_t	size;
1376 
1377 	if (status_prompt_hsize > 0 &&
1378 	    strcmp(status_prompt_hlist[status_prompt_hsize - 1], line) == 0)
1379 		return;
1380 
1381 	if (status_prompt_hsize == PROMPT_HISTORY) {
1382 		free(status_prompt_hlist[0]);
1383 
1384 		size = (PROMPT_HISTORY - 1) * sizeof *status_prompt_hlist;
1385 		memmove(&status_prompt_hlist[0], &status_prompt_hlist[1], size);
1386 
1387 		status_prompt_hlist[status_prompt_hsize - 1] = xstrdup(line);
1388 		return;
1389 	}
1390 
1391 	status_prompt_hlist = xreallocarray(status_prompt_hlist,
1392 	    status_prompt_hsize + 1, sizeof *status_prompt_hlist);
1393 	status_prompt_hlist[status_prompt_hsize++] = xstrdup(line);
1394 }
1395 
1396 /* Build completion list. */
1397 static const char **
1398 status_prompt_complete_list(u_int *size, const char *s)
1399 {
1400 	const char				**list = NULL, **layout;
1401 	const struct cmd_entry			**cmdent;
1402 	const struct options_table_entry	 *oe;
1403 	const char				 *layouts[] = {
1404 		"even-horizontal", "even-vertical", "main-horizontal",
1405 		"main-vertical", "tiled", NULL
1406 	};
1407 
1408 	*size = 0;
1409 	for (cmdent = cmd_table; *cmdent != NULL; cmdent++) {
1410 		if (strncmp((*cmdent)->name, s, strlen(s)) == 0) {
1411 			list = xreallocarray(list, (*size) + 1, sizeof *list);
1412 			list[(*size)++] = (*cmdent)->name;
1413 		}
1414 	}
1415 	for (oe = options_table; oe->name != NULL; oe++) {
1416 		if (strncmp(oe->name, s, strlen(s)) == 0) {
1417 			list = xreallocarray(list, (*size) + 1, sizeof *list);
1418 			list[(*size)++] = oe->name;
1419 		}
1420 	}
1421 	for (layout = layouts; *layout != NULL; layout++) {
1422 		if (strncmp(*layout, s, strlen(s)) == 0) {
1423 			list = xreallocarray(list, (*size) + 1, sizeof *list);
1424 			list[(*size)++] = *layout;
1425 		}
1426 	}
1427 	return (list);
1428 }
1429 
1430 /* Find longest prefix. */
1431 static char *
1432 status_prompt_complete_prefix(const char **list, u_int size)
1433 {
1434 	char	 *out;
1435 	u_int	  i;
1436 	size_t	  j;
1437 
1438 	out = xstrdup(list[0]);
1439 	for (i = 1; i < size; i++) {
1440 		j = strlen(list[i]);
1441 		if (j > strlen(out))
1442 			j = strlen(out);
1443 		for (; j > 0; j--) {
1444 			if (out[j - 1] != list[i][j - 1])
1445 				out[j - 1] = '\0';
1446 		}
1447 	}
1448 	return (out);
1449 }
1450 
1451 /* Complete word. */
1452 static char *
1453 status_prompt_complete(struct session *session, const char *s)
1454 {
1455 	const char	**list = NULL, *colon;
1456 	u_int		  size = 0, i;
1457 	struct session	 *s_loop;
1458 	struct winlink	 *wl;
1459 	struct window	 *w;
1460 	char		 *copy, *out, *tmp;
1461 
1462 	if (*s == '\0')
1463 		return (NULL);
1464 	out = NULL;
1465 
1466 	if (strncmp(s, "-t", 2) != 0 && strncmp(s, "-s", 2) != 0) {
1467 		list = status_prompt_complete_list(&size, s);
1468 		if (size == 0)
1469 			out = NULL;
1470 		else if (size == 1)
1471 			xasprintf(&out, "%s ", list[0]);
1472 		else
1473 			out = status_prompt_complete_prefix(list, size);
1474 		free(list);
1475 		return (out);
1476 	}
1477 	copy = xstrdup(s);
1478 
1479 	colon = ":";
1480 	if (copy[strlen(copy) - 1] == ':')
1481 		copy[strlen(copy) - 1] = '\0';
1482 	else
1483 		colon = "";
1484 	s = copy + 2;
1485 
1486 	RB_FOREACH(s_loop, sessions, &sessions) {
1487 		if (strncmp(s_loop->name, s, strlen(s)) == 0) {
1488 			list = xreallocarray(list, size + 2, sizeof *list);
1489 			list[size++] = s_loop->name;
1490 		}
1491 	}
1492 	if (size == 1) {
1493 		out = xstrdup(list[0]);
1494 		if (session_find(list[0]) != NULL)
1495 			colon = ":";
1496 	} else if (size != 0)
1497 		out = status_prompt_complete_prefix(list, size);
1498 	if (out != NULL) {
1499 		xasprintf(&tmp, "-%c%s%s", copy[1], out, colon);
1500 		out = tmp;
1501 		goto found;
1502 	}
1503 
1504 	colon = "";
1505 	if (*s == ':') {
1506 		RB_FOREACH(wl, winlinks, &session->windows) {
1507 			xasprintf(&tmp, ":%s", wl->window->name);
1508 			if (strncmp(tmp, s, strlen(s)) == 0){
1509 				list = xreallocarray(list, size + 1,
1510 				    sizeof *list);
1511 				list[size++] = tmp;
1512 				continue;
1513 			}
1514 			free(tmp);
1515 
1516 			xasprintf(&tmp, ":%d", wl->idx);
1517 			if (strncmp(tmp, s, strlen(s)) == 0) {
1518 				list = xreallocarray(list, size + 1,
1519 				    sizeof *list);
1520 				list[size++] = tmp;
1521 				continue;
1522 			}
1523 			free(tmp);
1524 		}
1525 	} else {
1526 		RB_FOREACH(s_loop, sessions, &sessions) {
1527 			RB_FOREACH(wl, winlinks, &s_loop->windows) {
1528 				w = wl->window;
1529 
1530 				xasprintf(&tmp, "%s:%s", s_loop->name, w->name);
1531 				if (strncmp(tmp, s, strlen(s)) == 0) {
1532 					list = xreallocarray(list, size + 1,
1533 					    sizeof *list);
1534 					list[size++] = tmp;
1535 					continue;
1536 				}
1537 				free(tmp);
1538 
1539 				xasprintf(&tmp, "%s:%d", s_loop->name, wl->idx);
1540 				if (strncmp(tmp, s, strlen(s)) == 0) {
1541 					list = xreallocarray(list, size + 1,
1542 					    sizeof *list);
1543 					list[size++] = tmp;
1544 					continue;
1545 				}
1546 				free(tmp);
1547 			}
1548 		}
1549 	}
1550 	if (size == 1) {
1551 		out = xstrdup(list[0]);
1552 		colon = " ";
1553 	} else if (size != 0)
1554 		out = status_prompt_complete_prefix(list, size);
1555 	if (out != NULL) {
1556 		xasprintf(&tmp, "-%c%s%s", copy[1], out, colon);
1557 		out = tmp;
1558 	}
1559 
1560 	for (i = 0; i < size; i++)
1561 		free((void *)list[i]);
1562 
1563 found:
1564 	free(copy);
1565 	free(list);
1566 	return (out);
1567 }
1568