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