xref: /openbsd-src/usr.bin/tmux/status.c (revision 99fd087599a8791921855f21bd7e36130f39aadc)
1 /* $OpenBSD: status.c,v 1.201 2020/01/27 08:53:13 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|CLIENT_CONTROL))
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|CLIENT_CONTROL))
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 		screen_resize(&sl->screen, width, lines, 0);
352 		changed = force = 1;
353 	}
354 	screen_write_start(&ctx, NULL, &sl->screen);
355 
356 	/* Create format tree. */
357 	flags = FORMAT_STATUS;
358 	if (c->flags & CLIENT_STATUSFORCE)
359 		flags |= FORMAT_FORCE;
360 	ft = format_create(c, NULL, FORMAT_NONE, flags);
361 	format_defaults(ft, c, NULL, NULL, NULL);
362 
363 	/* Write the status lines. */
364 	o = options_get(s->options, "status-format");
365 	if (o == NULL) {
366 		for (n = 0; n < width * lines; n++)
367 			screen_write_putc(&ctx, &gc, ' ');
368 	} else {
369 		for (i = 0; i < lines; i++) {
370 			screen_write_cursormove(&ctx, 0, i, 0);
371 
372 			ov = options_array_get(o, i);
373 			if (ov == NULL) {
374 				for (n = 0; n < width; n++)
375 					screen_write_putc(&ctx, &gc, ' ');
376 				continue;
377 			}
378 			sle = &sl->entries[i];
379 
380 			expanded = format_expand_time(ft, ov->string);
381 			if (!force &&
382 			    sle->expanded != NULL &&
383 			    strcmp(expanded, sle->expanded) == 0) {
384 				free(expanded);
385 				continue;
386 			}
387 			changed = 1;
388 
389 			for (n = 0; n < width; n++)
390 				screen_write_putc(&ctx, &gc, ' ');
391 			screen_write_cursormove(&ctx, 0, i, 0);
392 
393 			status_free_ranges(&sle->ranges);
394 			format_draw(&ctx, &gc, width, expanded, &sle->ranges);
395 
396 			free(sle->expanded);
397 			sle->expanded = expanded;
398 		}
399 	}
400 	screen_write_stop(&ctx);
401 
402 	/* Free the format tree. */
403 	format_free(ft);
404 
405 	/* Return if the status line has changed. */
406 	log_debug("%s exit: force=%d, changed=%d", __func__, force, changed);
407 	return (force || changed);
408 }
409 
410 /* Set a status line message. */
411 void
412 status_message_set(struct client *c, const char *fmt, ...)
413 {
414 	struct timeval	tv;
415 	va_list		ap;
416 	int		delay;
417 
418 	status_message_clear(c);
419 	status_push_screen(c);
420 
421 	va_start(ap, fmt);
422 	xvasprintf(&c->message_string, fmt, ap);
423 	va_end(ap);
424 
425 	server_client_add_message(c, "%s", c->message_string);
426 
427 	delay = options_get_number(c->session->options, "display-time");
428 	if (delay > 0) {
429 		tv.tv_sec = delay / 1000;
430 		tv.tv_usec = (delay % 1000) * 1000L;
431 
432 		if (event_initialized(&c->message_timer))
433 			evtimer_del(&c->message_timer);
434 		evtimer_set(&c->message_timer, status_message_callback, c);
435 		evtimer_add(&c->message_timer, &tv);
436 	}
437 
438 	c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
439 	c->flags |= CLIENT_REDRAWSTATUS;
440 }
441 
442 /* Clear status line message. */
443 void
444 status_message_clear(struct client *c)
445 {
446 	if (c->message_string == NULL)
447 		return;
448 
449 	free(c->message_string);
450 	c->message_string = NULL;
451 
452 	if (c->prompt_string == NULL)
453 		c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
454 	c->flags |= CLIENT_ALLREDRAWFLAGS; /* was frozen and may have changed */
455 
456 	status_pop_screen(c);
457 }
458 
459 /* Clear status line message after timer expires. */
460 static void
461 status_message_callback(__unused int fd, __unused short event, void *data)
462 {
463 	struct client	*c = data;
464 
465 	status_message_clear(c);
466 }
467 
468 /* Draw client message on status line of present else on last line. */
469 int
470 status_message_redraw(struct client *c)
471 {
472 	struct status_line	*sl = &c->status;
473 	struct screen_write_ctx	 ctx;
474 	struct session		*s = c->session;
475 	struct screen		 old_screen;
476 	size_t			 len;
477 	u_int			 lines, offset;
478 	struct grid_cell	 gc;
479 
480 	if (c->tty.sx == 0 || c->tty.sy == 0)
481 		return (0);
482 	memcpy(&old_screen, sl->active, sizeof old_screen);
483 
484 	lines = status_line_size(c);
485 	if (lines <= 1)
486 		lines = 1;
487 	screen_init(sl->active, c->tty.sx, lines, 0);
488 
489 	len = screen_write_strlen("%s", c->message_string);
490 	if (len > c->tty.sx)
491 		len = c->tty.sx;
492 
493 	style_apply(&gc, s->options, "message-style");
494 
495 	screen_write_start(&ctx, NULL, sl->active);
496 	screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines - 1);
497 	screen_write_cursormove(&ctx, 0, lines - 1, 0);
498 	for (offset = 0; offset < c->tty.sx; offset++)
499 		screen_write_putc(&ctx, &gc, ' ');
500 	screen_write_cursormove(&ctx, 0, lines - 1, 0);
501 	screen_write_nputs(&ctx, len, &gc, "%s", c->message_string);
502 	screen_write_stop(&ctx);
503 
504 	if (grid_compare(sl->active->grid, old_screen.grid) == 0) {
505 		screen_free(&old_screen);
506 		return (0);
507 	}
508 	screen_free(&old_screen);
509 	return (1);
510 }
511 
512 /* Enable status line prompt. */
513 void
514 status_prompt_set(struct client *c, const char *msg, const char *input,
515     prompt_input_cb inputcb, prompt_free_cb freecb, void *data, int flags)
516 {
517 	struct format_tree	*ft;
518 	char			*tmp, *cp;
519 
520 	ft = format_create(c, NULL, FORMAT_NONE, 0);
521 	format_defaults(ft, c, NULL, NULL, NULL);
522 
523 	if (input == NULL)
524 		input = "";
525 	if (flags & PROMPT_NOFORMAT)
526 		tmp = xstrdup(input);
527 	else
528 		tmp = format_expand_time(ft, input);
529 
530 	status_message_clear(c);
531 	status_prompt_clear(c);
532 	status_push_screen(c);
533 
534 	c->prompt_string = format_expand_time(ft, msg);
535 
536 	c->prompt_buffer = utf8_fromcstr(tmp);
537 	c->prompt_index = utf8_strlen(c->prompt_buffer);
538 
539 	c->prompt_inputcb = inputcb;
540 	c->prompt_freecb = freecb;
541 	c->prompt_data = data;
542 
543 	c->prompt_hindex = 0;
544 
545 	c->prompt_flags = flags;
546 	c->prompt_mode = PROMPT_ENTRY;
547 
548 	if (~flags & PROMPT_INCREMENTAL)
549 		c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
550 	c->flags |= CLIENT_REDRAWSTATUS;
551 
552 	if ((flags & PROMPT_INCREMENTAL) && *tmp != '\0') {
553 		xasprintf(&cp, "=%s", tmp);
554 		c->prompt_inputcb(c, c->prompt_data, cp, 0);
555 		free(cp);
556 	}
557 
558 	free(tmp);
559 	format_free(ft);
560 }
561 
562 /* Remove status line prompt. */
563 void
564 status_prompt_clear(struct client *c)
565 {
566 	if (c->prompt_string == NULL)
567 		return;
568 
569 	if (c->prompt_freecb != NULL && c->prompt_data != NULL)
570 		c->prompt_freecb(c->prompt_data);
571 
572 	free(c->prompt_string);
573 	c->prompt_string = NULL;
574 
575 	free(c->prompt_buffer);
576 	c->prompt_buffer = NULL;
577 
578 	free(c->prompt_saved);
579 	c->prompt_saved = NULL;
580 
581 	c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
582 	c->flags |= CLIENT_ALLREDRAWFLAGS; /* was frozen and may have changed */
583 
584 	status_pop_screen(c);
585 }
586 
587 /* Update status line prompt with a new prompt string. */
588 void
589 status_prompt_update(struct client *c, const char *msg, const char *input)
590 {
591 	struct format_tree	*ft;
592 	char			*tmp;
593 
594 	ft = format_create(c, NULL, FORMAT_NONE, 0);
595 	format_defaults(ft, c, NULL, NULL, NULL);
596 
597 	tmp = format_expand_time(ft, input);
598 
599 	free(c->prompt_string);
600 	c->prompt_string = format_expand_time(ft, msg);
601 
602 	free(c->prompt_buffer);
603 	c->prompt_buffer = utf8_fromcstr(tmp);
604 	c->prompt_index = utf8_strlen(c->prompt_buffer);
605 
606 	c->prompt_hindex = 0;
607 
608 	c->flags |= CLIENT_REDRAWSTATUS;
609 
610 	free(tmp);
611 	format_free(ft);
612 }
613 
614 /* Draw client prompt on status line of present else on last line. */
615 int
616 status_prompt_redraw(struct client *c)
617 {
618 	struct status_line	*sl = &c->status;
619 	struct screen_write_ctx	 ctx;
620 	struct session		*s = c->session;
621 	struct screen		 old_screen;
622 	u_int			 i, lines, offset, left, start, width;
623 	u_int			 pcursor, pwidth;
624 	struct grid_cell	 gc, cursorgc;
625 
626 	if (c->tty.sx == 0 || c->tty.sy == 0)
627 		return (0);
628 	memcpy(&old_screen, sl->active, sizeof old_screen);
629 
630 	lines = status_line_size(c);
631 	if (lines <= 1)
632 		lines = 1;
633 	screen_init(sl->active, c->tty.sx, lines, 0);
634 
635 	if (c->prompt_mode == PROMPT_COMMAND)
636 		style_apply(&gc, s->options, "message-command-style");
637 	else
638 		style_apply(&gc, s->options, "message-style");
639 
640 	memcpy(&cursorgc, &gc, sizeof cursorgc);
641 	cursorgc.attr ^= GRID_ATTR_REVERSE;
642 
643 	start = screen_write_strlen("%s", c->prompt_string);
644 	if (start > c->tty.sx)
645 		start = c->tty.sx;
646 
647 	screen_write_start(&ctx, NULL, sl->active);
648 	screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines - 1);
649 	screen_write_cursormove(&ctx, 0, lines - 1, 0);
650 	for (offset = 0; offset < c->tty.sx; offset++)
651 		screen_write_putc(&ctx, &gc, ' ');
652 	screen_write_cursormove(&ctx, 0, lines - 1, 0);
653 	screen_write_nputs(&ctx, start, &gc, "%s", c->prompt_string);
654 	screen_write_cursormove(&ctx, start, lines - 1, 0);
655 
656 	left = c->tty.sx - start;
657 	if (left == 0)
658 		goto finished;
659 
660 	pcursor = utf8_strwidth(c->prompt_buffer, c->prompt_index);
661 	pwidth = utf8_strwidth(c->prompt_buffer, -1);
662 	if (pcursor >= left) {
663 		/*
664 		 * The cursor would be outside the screen so start drawing
665 		 * with it on the right.
666 		 */
667 		offset = (pcursor - left) + 1;
668 		pwidth = left;
669 	} else
670 		offset = 0;
671 	if (pwidth > left)
672 		pwidth = left;
673 
674 	width = 0;
675 	for (i = 0; c->prompt_buffer[i].size != 0; i++) {
676 		if (width < offset) {
677 			width += c->prompt_buffer[i].width;
678 			continue;
679 		}
680 		if (width >= offset + pwidth)
681 			break;
682 		width += c->prompt_buffer[i].width;
683 		if (width > offset + pwidth)
684 			break;
685 
686 		if (i != c->prompt_index) {
687 			utf8_copy(&gc.data, &c->prompt_buffer[i]);
688 			screen_write_cell(&ctx, &gc);
689 		} else {
690 			utf8_copy(&cursorgc.data, &c->prompt_buffer[i]);
691 			screen_write_cell(&ctx, &cursorgc);
692 		}
693 	}
694 	if (sl->active->cx < screen_size_x(sl->active) && c->prompt_index >= i)
695 		screen_write_putc(&ctx, &cursorgc, ' ');
696 
697 finished:
698 	screen_write_stop(&ctx);
699 
700 	if (grid_compare(sl->active->grid, old_screen.grid) == 0) {
701 		screen_free(&old_screen);
702 		return (0);
703 	}
704 	screen_free(&old_screen);
705 	return (1);
706 }
707 
708 /* Is this a separator? */
709 static int
710 status_prompt_in_list(const char *ws, const struct utf8_data *ud)
711 {
712 	if (ud->size != 1 || ud->width != 1)
713 		return (0);
714 	return (strchr(ws, *ud->data) != NULL);
715 }
716 
717 /* Is this a space? */
718 static int
719 status_prompt_space(const struct utf8_data *ud)
720 {
721 	if (ud->size != 1 || ud->width != 1)
722 		return (0);
723 	return (*ud->data == ' ');
724 }
725 
726 /*
727  * Translate key from emacs to vi. Return 0 to drop key, 1 to process the key
728  * as an emacs key; return 2 to append to the buffer.
729  */
730 static int
731 status_prompt_translate_key(struct client *c, key_code key, key_code *new_key)
732 {
733 	if (c->prompt_mode == PROMPT_ENTRY) {
734 		switch (key) {
735 		case '\003': /* C-c */
736 		case '\010': /* C-h */
737 		case '\011': /* Tab */
738 		case '\025': /* C-u */
739 		case '\027': /* C-w */
740 		case '\n':
741 		case '\r':
742 		case KEYC_BSPACE:
743 		case KEYC_DC:
744 		case KEYC_DOWN:
745 		case KEYC_END:
746 		case KEYC_HOME:
747 		case KEYC_LEFT:
748 		case KEYC_RIGHT:
749 		case KEYC_UP:
750 			*new_key = key;
751 			return (1);
752 		case '\033': /* Escape */
753 			c->prompt_mode = PROMPT_COMMAND;
754 			c->flags |= CLIENT_REDRAWSTATUS;
755 			return (0);
756 		}
757 		*new_key = key;
758 		return (2);
759 	}
760 
761 	switch (key) {
762 	case 'A':
763 	case 'I':
764 	case 'C':
765 	case 's':
766 	case 'a':
767 		c->prompt_mode = PROMPT_ENTRY;
768 		c->flags |= CLIENT_REDRAWSTATUS;
769 		break; /* switch mode and... */
770 	case 'S':
771 		c->prompt_mode = PROMPT_ENTRY;
772 		c->flags |= CLIENT_REDRAWSTATUS;
773 		*new_key = '\025'; /* C-u */
774 		return (1);
775 	case 'i':
776 	case '\033': /* Escape */
777 		c->prompt_mode = PROMPT_ENTRY;
778 		c->flags |= CLIENT_REDRAWSTATUS;
779 		return (0);
780 	}
781 
782 	switch (key) {
783 	case 'A':
784 	case '$':
785 		*new_key = KEYC_END;
786 		return (1);
787 	case 'I':
788 	case '0':
789 	case '^':
790 		*new_key = KEYC_HOME;
791 		return (1);
792 	case 'C':
793 	case 'D':
794 		*new_key = '\013'; /* C-k */
795 		return (1);
796 	case KEYC_BSPACE:
797 	case 'X':
798 		*new_key = KEYC_BSPACE;
799 		return (1);
800 	case 'b':
801 	case 'B':
802 		*new_key = 'b'|KEYC_ESCAPE;
803 		return (1);
804 	case 'd':
805 		*new_key = '\025';
806 		return (1);
807 	case 'e':
808 	case 'E':
809 	case 'w':
810 	case 'W':
811 		*new_key = 'f'|KEYC_ESCAPE;
812 		return (1);
813 	case 'p':
814 		*new_key = '\031'; /* C-y */
815 		return (1);
816 	case 's':
817 	case KEYC_DC:
818 	case 'x':
819 		*new_key = KEYC_DC;
820 		return (1);
821 	case KEYC_DOWN:
822 	case 'j':
823 		*new_key = KEYC_DOWN;
824 		return (1);
825 	case KEYC_LEFT:
826 	case 'h':
827 		*new_key = KEYC_LEFT;
828 		return (1);
829 	case 'a':
830 	case KEYC_RIGHT:
831 	case 'l':
832 		*new_key = KEYC_RIGHT;
833 		return (1);
834 	case KEYC_UP:
835 	case 'k':
836 		*new_key = KEYC_UP;
837 		return (1);
838 	case '\010' /* C-h */:
839 	case '\003' /* C-c */:
840 	case '\n':
841 	case '\r':
842 		return (1);
843 	}
844 	return (0);
845 }
846 
847 /* Paste into prompt. */
848 static int
849 status_prompt_paste(struct client *c)
850 {
851 	struct paste_buffer	*pb;
852 	const char		*bufdata;
853 	size_t			 size, n, bufsize;
854 	u_int			 i;
855 	struct utf8_data	*ud, *udp;
856 	enum utf8_state		 more;
857 
858 	size = utf8_strlen(c->prompt_buffer);
859 	if (c->prompt_saved != NULL) {
860 		ud = c->prompt_saved;
861 		n = utf8_strlen(c->prompt_saved);
862 	} else {
863 		if ((pb = paste_get_top(NULL)) == NULL)
864 			return (0);
865 		bufdata = paste_buffer_data(pb, &bufsize);
866 		ud = xreallocarray(NULL, bufsize + 1, sizeof *ud);
867 		udp = ud;
868 		for (i = 0; i != bufsize; /* nothing */) {
869 			more = utf8_open(udp, bufdata[i]);
870 			if (more == UTF8_MORE) {
871 				while (++i != bufsize && more == UTF8_MORE)
872 					more = utf8_append(udp, bufdata[i]);
873 				if (more == UTF8_DONE) {
874 					udp++;
875 					continue;
876 				}
877 				i -= udp->have;
878 			}
879 			if (bufdata[i] <= 31 || bufdata[i] >= 127)
880 				break;
881 			utf8_set(udp, bufdata[i]);
882 			udp++;
883 			i++;
884 		}
885 		udp->size = 0;
886 		n = udp - ud;
887 	}
888 	if (n == 0)
889 		return (0);
890 
891 	c->prompt_buffer = xreallocarray(c->prompt_buffer, size + n + 1,
892 	    sizeof *c->prompt_buffer);
893 	if (c->prompt_index == size) {
894 		memcpy(c->prompt_buffer + c->prompt_index, ud,
895 		    n * sizeof *c->prompt_buffer);
896 		c->prompt_index += n;
897 		c->prompt_buffer[c->prompt_index].size = 0;
898 	} else {
899 		memmove(c->prompt_buffer + c->prompt_index + n,
900 		    c->prompt_buffer + c->prompt_index,
901 		    (size + 1 - c->prompt_index) * sizeof *c->prompt_buffer);
902 		memcpy(c->prompt_buffer + c->prompt_index, ud,
903 		    n * sizeof *c->prompt_buffer);
904 		c->prompt_index += n;
905 	}
906 
907 	if (ud != c->prompt_saved)
908 		free(ud);
909 	return (1);
910 }
911 
912 /* Handle keys in prompt. */
913 int
914 status_prompt_key(struct client *c, key_code key)
915 {
916 	struct options		*oo = c->session->options;
917 	char			*s, *cp, word[64], prefix = '=';
918 	const char		*histstr, *ws = NULL, *keystring;
919 	size_t			 size, n, off, idx, used;
920 	struct utf8_data	 tmp, *first, *last, *ud;
921 	int			 keys;
922 
923 	if (c->prompt_flags & PROMPT_KEY) {
924 		keystring = key_string_lookup_key(key);
925 		c->prompt_inputcb(c, c->prompt_data, keystring, 1);
926 		status_prompt_clear(c);
927 		return (0);
928 	}
929 	size = utf8_strlen(c->prompt_buffer);
930 
931 	if (c->prompt_flags & PROMPT_NUMERIC) {
932 		if (key >= '0' && key <= '9')
933 			goto append_key;
934 		s = utf8_tocstr(c->prompt_buffer);
935 		c->prompt_inputcb(c, c->prompt_data, s, 1);
936 		status_prompt_clear(c);
937 		free(s);
938 		return (1);
939 	}
940 	key &= ~KEYC_XTERM;
941 
942 	keys = options_get_number(c->session->options, "status-keys");
943 	if (keys == MODEKEY_VI) {
944 		switch (status_prompt_translate_key(c, key, &key)) {
945 		case 1:
946 			goto process_key;
947 		case 2:
948 			goto append_key;
949 		default:
950 			return (0);
951 		}
952 	}
953 
954 process_key:
955 	switch (key) {
956 	case KEYC_LEFT:
957 	case '\002': /* C-b */
958 		if (c->prompt_index > 0) {
959 			c->prompt_index--;
960 			break;
961 		}
962 		break;
963 	case KEYC_RIGHT:
964 	case '\006': /* C-f */
965 		if (c->prompt_index < size) {
966 			c->prompt_index++;
967 			break;
968 		}
969 		break;
970 	case KEYC_HOME:
971 	case '\001': /* C-a */
972 		if (c->prompt_index != 0) {
973 			c->prompt_index = 0;
974 			break;
975 		}
976 		break;
977 	case KEYC_END:
978 	case '\005': /* C-e */
979 		if (c->prompt_index != size) {
980 			c->prompt_index = size;
981 			break;
982 		}
983 		break;
984 	case '\011': /* Tab */
985 		if (c->prompt_buffer[0].size == 0)
986 			break;
987 
988 		idx = c->prompt_index;
989 		if (idx != 0)
990 			idx--;
991 
992 		/* Find the word we are in. */
993 		first = &c->prompt_buffer[idx];
994 		while (first > c->prompt_buffer && !status_prompt_space(first))
995 			first--;
996 		while (first->size != 0 && status_prompt_space(first))
997 			first++;
998 		last = &c->prompt_buffer[idx];
999 		while (last->size != 0 && !status_prompt_space(last))
1000 			last++;
1001 		while (last > c->prompt_buffer && status_prompt_space(last))
1002 			last--;
1003 		if (last->size != 0)
1004 			last++;
1005 		if (last <= first)
1006 			break;
1007 
1008 		used = 0;
1009 		for (ud = first; ud < last; ud++) {
1010 			if (used + ud->size >= sizeof word)
1011 				break;
1012 			memcpy(word + used, ud->data, ud->size);
1013 			used += ud->size;
1014 		}
1015 		if (ud != last)
1016 			break;
1017 		word[used] = '\0';
1018 
1019 		/* And try to complete it. */
1020 		if ((s = status_prompt_complete(c->session, word)) == NULL)
1021 			break;
1022 
1023 		/* Trim out word. */
1024 		n = size - (last - c->prompt_buffer) + 1; /* with \0 */
1025 		memmove(first, last, n * sizeof *c->prompt_buffer);
1026 		size -= last - first;
1027 
1028 		/* Insert the new word. */
1029 		size += strlen(s);
1030 		off = first - c->prompt_buffer;
1031 		c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 1,
1032 		    sizeof *c->prompt_buffer);
1033 		first = c->prompt_buffer + off;
1034 		memmove(first + strlen(s), first, n * sizeof *c->prompt_buffer);
1035 		for (idx = 0; idx < strlen(s); idx++)
1036 			utf8_set(&first[idx], s[idx]);
1037 
1038 		c->prompt_index = (first - c->prompt_buffer) + strlen(s);
1039 		free(s);
1040 
1041 		goto changed;
1042 	case KEYC_BSPACE:
1043 	case '\010': /* C-h */
1044 		if (c->prompt_index != 0) {
1045 			if (c->prompt_index == size)
1046 				c->prompt_buffer[--c->prompt_index].size = 0;
1047 			else {
1048 				memmove(c->prompt_buffer + c->prompt_index - 1,
1049 				    c->prompt_buffer + c->prompt_index,
1050 				    (size + 1 - c->prompt_index) *
1051 				    sizeof *c->prompt_buffer);
1052 				c->prompt_index--;
1053 			}
1054 			goto changed;
1055 		}
1056 		break;
1057 	case KEYC_DC:
1058 	case '\004': /* C-d */
1059 		if (c->prompt_index != size) {
1060 			memmove(c->prompt_buffer + c->prompt_index,
1061 			    c->prompt_buffer + c->prompt_index + 1,
1062 			    (size + 1 - c->prompt_index) *
1063 			    sizeof *c->prompt_buffer);
1064 			goto changed;
1065 		}
1066 		break;
1067 	case '\025': /* C-u */
1068 		c->prompt_buffer[0].size = 0;
1069 		c->prompt_index = 0;
1070 		goto changed;
1071 	case '\013': /* C-k */
1072 		if (c->prompt_index < size) {
1073 			c->prompt_buffer[c->prompt_index].size = 0;
1074 			goto changed;
1075 		}
1076 		break;
1077 	case '\027': /* C-w */
1078 		ws = options_get_string(oo, "word-separators");
1079 		idx = c->prompt_index;
1080 
1081 		/* Find a non-separator. */
1082 		while (idx != 0) {
1083 			idx--;
1084 			if (!status_prompt_in_list(ws, &c->prompt_buffer[idx]))
1085 				break;
1086 		}
1087 
1088 		/* Find the separator at the beginning of the word. */
1089 		while (idx != 0) {
1090 			idx--;
1091 			if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) {
1092 				/* Go back to the word. */
1093 				idx++;
1094 				break;
1095 			}
1096 		}
1097 
1098 		free(c->prompt_saved);
1099 		c->prompt_saved = xcalloc(sizeof *c->prompt_buffer,
1100 		    (c->prompt_index - idx) + 1);
1101 		memcpy(c->prompt_saved, c->prompt_buffer + idx,
1102 		    (c->prompt_index - idx) * sizeof *c->prompt_buffer);
1103 
1104 		memmove(c->prompt_buffer + idx,
1105 		    c->prompt_buffer + c->prompt_index,
1106 		    (size + 1 - c->prompt_index) *
1107 		    sizeof *c->prompt_buffer);
1108 		memset(c->prompt_buffer + size - (c->prompt_index - idx),
1109 		    '\0', (c->prompt_index - idx) * sizeof *c->prompt_buffer);
1110 		c->prompt_index = idx;
1111 
1112 		goto changed;
1113 	case 'f'|KEYC_ESCAPE:
1114 	case KEYC_RIGHT|KEYC_CTRL:
1115 		ws = options_get_string(oo, "word-separators");
1116 
1117 		/* Find a word. */
1118 		while (c->prompt_index != size) {
1119 			idx = ++c->prompt_index;
1120 			if (!status_prompt_in_list(ws, &c->prompt_buffer[idx]))
1121 				break;
1122 		}
1123 
1124 		/* Find the separator at the end of the word. */
1125 		while (c->prompt_index != size) {
1126 			idx = ++c->prompt_index;
1127 			if (status_prompt_in_list(ws, &c->prompt_buffer[idx]))
1128 				break;
1129 		}
1130 
1131 		/* Back up to the end-of-word like vi. */
1132 		if (options_get_number(oo, "status-keys") == MODEKEY_VI &&
1133 		    c->prompt_index != 0)
1134 			c->prompt_index--;
1135 
1136 		goto changed;
1137 	case 'b'|KEYC_ESCAPE:
1138 	case KEYC_LEFT|KEYC_CTRL:
1139 		ws = options_get_string(oo, "word-separators");
1140 
1141 		/* Find a non-separator. */
1142 		while (c->prompt_index != 0) {
1143 			idx = --c->prompt_index;
1144 			if (!status_prompt_in_list(ws, &c->prompt_buffer[idx]))
1145 				break;
1146 		}
1147 
1148 		/* Find the separator at the beginning of the word. */
1149 		while (c->prompt_index != 0) {
1150 			idx = --c->prompt_index;
1151 			if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) {
1152 				/* Go back to the word. */
1153 				c->prompt_index++;
1154 				break;
1155 			}
1156 		}
1157 		goto changed;
1158 	case KEYC_UP:
1159 	case '\020': /* C-p */
1160 		histstr = status_prompt_up_history(&c->prompt_hindex);
1161 		if (histstr == NULL)
1162 			break;
1163 		free(c->prompt_buffer);
1164 		c->prompt_buffer = utf8_fromcstr(histstr);
1165 		c->prompt_index = utf8_strlen(c->prompt_buffer);
1166 		goto changed;
1167 	case KEYC_DOWN:
1168 	case '\016': /* C-n */
1169 		histstr = status_prompt_down_history(&c->prompt_hindex);
1170 		if (histstr == NULL)
1171 			break;
1172 		free(c->prompt_buffer);
1173 		c->prompt_buffer = utf8_fromcstr(histstr);
1174 		c->prompt_index = utf8_strlen(c->prompt_buffer);
1175 		goto changed;
1176 	case '\031': /* C-y */
1177 		if (status_prompt_paste(c))
1178 			goto changed;
1179 		break;
1180 	case '\024': /* C-t */
1181 		idx = c->prompt_index;
1182 		if (idx < size)
1183 			idx++;
1184 		if (idx >= 2) {
1185 			utf8_copy(&tmp, &c->prompt_buffer[idx - 2]);
1186 			utf8_copy(&c->prompt_buffer[idx - 2],
1187 			    &c->prompt_buffer[idx - 1]);
1188 			utf8_copy(&c->prompt_buffer[idx - 1], &tmp);
1189 			c->prompt_index = idx;
1190 			goto changed;
1191 		}
1192 		break;
1193 	case '\r':
1194 	case '\n':
1195 		s = utf8_tocstr(c->prompt_buffer);
1196 		if (*s != '\0')
1197 			status_prompt_add_history(s);
1198 		if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0)
1199 			status_prompt_clear(c);
1200 		free(s);
1201 		break;
1202 	case '\033': /* Escape */
1203 	case '\003': /* C-c */
1204 	case '\007': /* C-g */
1205 		if (c->prompt_inputcb(c, c->prompt_data, NULL, 1) == 0)
1206 			status_prompt_clear(c);
1207 		break;
1208 	case '\022': /* C-r */
1209 		if (c->prompt_flags & PROMPT_INCREMENTAL) {
1210 			prefix = '-';
1211 			goto changed;
1212 		}
1213 		break;
1214 	case '\023': /* C-s */
1215 		if (c->prompt_flags & PROMPT_INCREMENTAL) {
1216 			prefix = '+';
1217 			goto changed;
1218 		}
1219 		break;
1220 	default:
1221 		goto append_key;
1222 	}
1223 
1224 	c->flags |= CLIENT_REDRAWSTATUS;
1225 	return (0);
1226 
1227 append_key:
1228 	if (key <= 0x1f || key >= KEYC_BASE)
1229 		return (0);
1230 	if (utf8_split(key, &tmp) != UTF8_DONE)
1231 		return (0);
1232 
1233 	c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 2,
1234 	    sizeof *c->prompt_buffer);
1235 
1236 	if (c->prompt_index == size) {
1237 		utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp);
1238 		c->prompt_index++;
1239 		c->prompt_buffer[c->prompt_index].size = 0;
1240 	} else {
1241 		memmove(c->prompt_buffer + c->prompt_index + 1,
1242 		    c->prompt_buffer + c->prompt_index,
1243 		    (size + 1 - c->prompt_index) *
1244 		    sizeof *c->prompt_buffer);
1245 		utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp);
1246 		c->prompt_index++;
1247 	}
1248 
1249 	if (c->prompt_flags & PROMPT_SINGLE) {
1250 		s = utf8_tocstr(c->prompt_buffer);
1251 		if (strlen(s) != 1)
1252 			status_prompt_clear(c);
1253 		else if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0)
1254 			status_prompt_clear(c);
1255 		free(s);
1256 	}
1257 
1258 changed:
1259 	c->flags |= CLIENT_REDRAWSTATUS;
1260 	if (c->prompt_flags & PROMPT_INCREMENTAL) {
1261 		s = utf8_tocstr(c->prompt_buffer);
1262 		xasprintf(&cp, "%c%s", prefix, s);
1263 		c->prompt_inputcb(c, c->prompt_data, cp, 0);
1264 		free(cp);
1265 		free(s);
1266 	}
1267 	return (0);
1268 }
1269 
1270 /* Get previous line from the history. */
1271 static const char *
1272 status_prompt_up_history(u_int *idx)
1273 {
1274 	/*
1275 	 * History runs from 0 to size - 1. Index is from 0 to size. Zero is
1276 	 * empty.
1277 	 */
1278 
1279 	if (status_prompt_hsize == 0 || *idx == status_prompt_hsize)
1280 		return (NULL);
1281 	(*idx)++;
1282 	return (status_prompt_hlist[status_prompt_hsize - *idx]);
1283 }
1284 
1285 /* Get next line from the history. */
1286 static const char *
1287 status_prompt_down_history(u_int *idx)
1288 {
1289 	if (status_prompt_hsize == 0 || *idx == 0)
1290 		return ("");
1291 	(*idx)--;
1292 	if (*idx == 0)
1293 		return ("");
1294 	return (status_prompt_hlist[status_prompt_hsize - *idx]);
1295 }
1296 
1297 /* Add line to the history. */
1298 static void
1299 status_prompt_add_history(const char *line)
1300 {
1301 	size_t	size;
1302 
1303 	if (status_prompt_hsize > 0 &&
1304 	    strcmp(status_prompt_hlist[status_prompt_hsize - 1], line) == 0)
1305 		return;
1306 
1307 	if (status_prompt_hsize == PROMPT_HISTORY) {
1308 		free(status_prompt_hlist[0]);
1309 
1310 		size = (PROMPT_HISTORY - 1) * sizeof *status_prompt_hlist;
1311 		memmove(&status_prompt_hlist[0], &status_prompt_hlist[1], size);
1312 
1313 		status_prompt_hlist[status_prompt_hsize - 1] = xstrdup(line);
1314 		return;
1315 	}
1316 
1317 	status_prompt_hlist = xreallocarray(status_prompt_hlist,
1318 	    status_prompt_hsize + 1, sizeof *status_prompt_hlist);
1319 	status_prompt_hlist[status_prompt_hsize++] = xstrdup(line);
1320 }
1321 
1322 /* Build completion list. */
1323 char **
1324 status_prompt_complete_list(u_int *size, const char *s)
1325 {
1326 	char					**list = NULL;
1327 	const char				**layout, *value, *cp;
1328 	const struct cmd_entry			**cmdent;
1329 	const struct options_table_entry	 *oe;
1330 	u_int					  idx;
1331 	size_t					  slen = strlen(s), valuelen;
1332 	struct options_entry			 *o;
1333 	struct options_array_item		 *a;
1334 	const char				 *layouts[] = {
1335 		"even-horizontal", "even-vertical", "main-horizontal",
1336 		"main-vertical", "tiled", NULL
1337 	};
1338 
1339 	*size = 0;
1340 	for (cmdent = cmd_table; *cmdent != NULL; cmdent++) {
1341 		if (strncmp((*cmdent)->name, s, slen) == 0) {
1342 			list = xreallocarray(list, (*size) + 1, sizeof *list);
1343 			list[(*size)++] = xstrdup((*cmdent)->name);
1344 		}
1345 	}
1346 	for (oe = options_table; oe->name != NULL; oe++) {
1347 		if (strncmp(oe->name, s, slen) == 0) {
1348 			list = xreallocarray(list, (*size) + 1, sizeof *list);
1349 			list[(*size)++] = xstrdup(oe->name);
1350 		}
1351 	}
1352 	for (layout = layouts; *layout != NULL; layout++) {
1353 		if (strncmp(*layout, s, slen) == 0) {
1354 			list = xreallocarray(list, (*size) + 1, sizeof *list);
1355 			list[(*size)++] = xstrdup(*layout);
1356 		}
1357 	}
1358 	o = options_get_only(global_options, "command-alias");
1359 	if (o != NULL) {
1360 		a = options_array_first(o);
1361 		while (a != NULL) {
1362 			value = options_array_item_value(a)->string;
1363 			if ((cp = strchr(value, '=')) == NULL)
1364 				goto next;
1365 			valuelen = cp - value;
1366 			if (slen > valuelen || strncmp(value, s, slen) != 0)
1367 				goto next;
1368 
1369 			list = xreallocarray(list, (*size) + 1, sizeof *list);
1370 			list[(*size)++] = xstrndup(value, valuelen);
1371 
1372 		next:
1373 			a = options_array_next(a);
1374 		}
1375 	}
1376 	for (idx = 0; idx < (*size); idx++)
1377 		log_debug("complete %u: %s", idx, list[idx]);
1378 	return (list);
1379 }
1380 
1381 /* Find longest prefix. */
1382 static char *
1383 status_prompt_complete_prefix(char **list, u_int size)
1384 {
1385 	char	 *out;
1386 	u_int	  i;
1387 	size_t	  j;
1388 
1389 	out = xstrdup(list[0]);
1390 	for (i = 1; i < size; i++) {
1391 		j = strlen(list[i]);
1392 		if (j > strlen(out))
1393 			j = strlen(out);
1394 		for (; j > 0; j--) {
1395 			if (out[j - 1] != list[i][j - 1])
1396 				out[j - 1] = '\0';
1397 		}
1398 	}
1399 	return (out);
1400 }
1401 
1402 /* Complete word. */
1403 static char *
1404 status_prompt_complete(struct session *session, const char *s)
1405 {
1406 	char		**list = NULL;
1407 	const char	 *colon;
1408 	u_int		  size = 0, i;
1409 	struct session	 *s_loop;
1410 	struct winlink	 *wl;
1411 	struct window	 *w;
1412 	char		 *copy, *out, *tmp;
1413 
1414 	if (*s == '\0')
1415 		return (NULL);
1416 	out = NULL;
1417 
1418 	if (strncmp(s, "-t", 2) != 0 && strncmp(s, "-s", 2) != 0) {
1419 		list = status_prompt_complete_list(&size, s);
1420 		if (size == 0)
1421 			out = NULL;
1422 		else if (size == 1)
1423 			xasprintf(&out, "%s ", list[0]);
1424 		else
1425 			out = status_prompt_complete_prefix(list, size);
1426 		for (i = 0; i < size; i++)
1427 			free(list[i]);
1428 		free(list);
1429 		return (out);
1430 	}
1431 	copy = xstrdup(s);
1432 
1433 	colon = ":";
1434 	if (copy[strlen(copy) - 1] == ':')
1435 		copy[strlen(copy) - 1] = '\0';
1436 	else
1437 		colon = "";
1438 	s = copy + 2;
1439 
1440 	RB_FOREACH(s_loop, sessions, &sessions) {
1441 		if (strncmp(s_loop->name, s, strlen(s)) == 0) {
1442 			list = xreallocarray(list, size + 2, sizeof *list);
1443 			list[size++] = s_loop->name;
1444 		}
1445 	}
1446 	if (size == 1) {
1447 		out = xstrdup(list[0]);
1448 		if (session_find(list[0]) != NULL)
1449 			colon = ":";
1450 	} else if (size != 0)
1451 		out = status_prompt_complete_prefix(list, size);
1452 	if (out != NULL) {
1453 		xasprintf(&tmp, "-%c%s%s", copy[1], out, colon);
1454 		free(out);
1455 		out = tmp;
1456 		goto found;
1457 	}
1458 
1459 	colon = "";
1460 	if (*s == ':') {
1461 		RB_FOREACH(wl, winlinks, &session->windows) {
1462 			xasprintf(&tmp, ":%s", wl->window->name);
1463 			if (strncmp(tmp, s, strlen(s)) == 0){
1464 				list = xreallocarray(list, size + 1,
1465 				    sizeof *list);
1466 				list[size++] = tmp;
1467 				continue;
1468 			}
1469 			free(tmp);
1470 
1471 			xasprintf(&tmp, ":%d", wl->idx);
1472 			if (strncmp(tmp, s, strlen(s)) == 0) {
1473 				list = xreallocarray(list, size + 1,
1474 				    sizeof *list);
1475 				list[size++] = tmp;
1476 				continue;
1477 			}
1478 			free(tmp);
1479 		}
1480 	} else {
1481 		RB_FOREACH(s_loop, sessions, &sessions) {
1482 			RB_FOREACH(wl, winlinks, &s_loop->windows) {
1483 				w = wl->window;
1484 
1485 				xasprintf(&tmp, "%s:%s", s_loop->name, w->name);
1486 				if (strncmp(tmp, s, strlen(s)) == 0) {
1487 					list = xreallocarray(list, size + 1,
1488 					    sizeof *list);
1489 					list[size++] = tmp;
1490 					continue;
1491 				}
1492 				free(tmp);
1493 
1494 				xasprintf(&tmp, "%s:%d", s_loop->name, wl->idx);
1495 				if (strncmp(tmp, s, strlen(s)) == 0) {
1496 					list = xreallocarray(list, size + 1,
1497 					    sizeof *list);
1498 					list[size++] = tmp;
1499 					continue;
1500 				}
1501 				free(tmp);
1502 			}
1503 		}
1504 	}
1505 	if (size == 1) {
1506 		out = xstrdup(list[0]);
1507 		colon = " ";
1508 	} else if (size != 0)
1509 		out = status_prompt_complete_prefix(list, size);
1510 	if (out != NULL) {
1511 		xasprintf(&tmp, "-%c%s%s", copy[1], out, colon);
1512 		out = tmp;
1513 	}
1514 
1515 	for (i = 0; i < size; i++)
1516 		free((void *)list[i]);
1517 
1518 found:
1519 	free(copy);
1520 	free(list);
1521 	return (out);
1522 }
1523