xref: /openbsd-src/usr.bin/tmux/status.c (revision 4c1e55dc91edd6e69ccc60ce855900fbc12cf34f)
1 /* $OpenBSD: status.c,v 1.94 2012/07/10 11:53:01 nicm Exp $ */
2 
3 /*
4  * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
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 char   *status_redraw_get_left(
33 	    struct client *, time_t, int, struct grid_cell *, size_t *);
34 char   *status_redraw_get_right(
35 	    struct client *, time_t, int, struct grid_cell *, size_t *);
36 char   *status_find_job(struct client *, char **);
37 void	status_job_free(void *);
38 void	status_job_callback(struct job *);
39 char   *status_print(
40 	    struct client *, struct winlink *, time_t, struct grid_cell *);
41 void	status_replace1(struct client *, struct session *, struct winlink *,
42 	    struct window_pane *, char **, char **, char *, size_t, int);
43 void	status_message_callback(int, short, void *);
44 
45 const char *status_prompt_up_history(u_int *);
46 const char *status_prompt_down_history(u_int *);
47 void	status_prompt_add_history(const char *);
48 char   *status_prompt_complete(const char *);
49 
50 /* Status prompt history. */
51 ARRAY_DECL(, char *) status_prompt_history = ARRAY_INITIALIZER;
52 
53 /* Status output tree. */
54 RB_GENERATE(status_out_tree, status_out, entry, status_out_cmp);
55 
56 /* Output tree comparison function. */
57 int
58 status_out_cmp(struct status_out *so1, struct status_out *so2)
59 {
60 	return (strcmp(so1->cmd, so2->cmd));
61 }
62 
63 /* Get screen line of status line. -1 means off. */
64 int
65 status_at_line(struct client *c)
66 {
67 	struct session	*s = c->session;
68 
69 	if (!options_get_number(&s->options, "status"))
70 		return (-1);
71 
72 	if (options_get_number(&s->options, "status-position") == 0)
73 		return (0);
74 	return (c->tty.sy - 1);
75 }
76 
77 /* Retrieve options for left string. */
78 char *
79 status_redraw_get_left(struct client *c,
80     time_t t, int utf8flag, struct grid_cell *gc, size_t *size)
81 {
82 	struct session	*s = c->session;
83 	char		*left;
84 	int		 fg, bg, attr;
85 	size_t		 leftlen;
86 
87 	fg = options_get_number(&s->options, "status-left-fg");
88 	if (fg != 8)
89 		colour_set_fg(gc, fg);
90 	bg = options_get_number(&s->options, "status-left-bg");
91 	if (bg != 8)
92 		colour_set_bg(gc, bg);
93 	attr = options_get_number(&s->options, "status-left-attr");
94 	if (attr != 0)
95 		gc->attr = attr;
96 
97 	left = status_replace(c, NULL,
98 	    NULL, NULL, options_get_string(&s->options, "status-left"), t, 1);
99 
100 	*size = options_get_number(&s->options, "status-left-length");
101 	leftlen = screen_write_cstrlen(utf8flag, "%s", left);
102 	if (leftlen < *size)
103 		*size = leftlen;
104 	return (left);
105 }
106 
107 /* Retrieve options for right string. */
108 char *
109 status_redraw_get_right(struct client *c,
110     time_t t, int utf8flag, struct grid_cell *gc, size_t *size)
111 {
112 	struct session	*s = c->session;
113 	char		*right;
114 	int		 fg, bg, attr;
115 	size_t		 rightlen;
116 
117 	fg = options_get_number(&s->options, "status-right-fg");
118 	if (fg != 8)
119 		colour_set_fg(gc, fg);
120 	bg = options_get_number(&s->options, "status-right-bg");
121 	if (bg != 8)
122 		colour_set_bg(gc, bg);
123 	attr = options_get_number(&s->options, "status-right-attr");
124 	if (attr != 0)
125 		gc->attr = attr;
126 
127 	right = status_replace(c, NULL,
128 	    NULL, NULL, options_get_string(&s->options, "status-right"), t, 1);
129 
130 	*size = options_get_number(&s->options, "status-right-length");
131 	rightlen = screen_write_cstrlen(utf8flag, "%s", right);
132 	if (rightlen < *size)
133 		*size = rightlen;
134 	return (right);
135 }
136 
137 /* Set window at window list position. */
138 void
139 status_set_window_at(struct client *c, u_int x)
140 {
141 	struct session	*s = c->session;
142 	struct winlink	*wl;
143 
144 	x += c->wlmouse;
145 	RB_FOREACH(wl, winlinks, &s->windows) {
146 		if (x < wl->status_width &&
147 			session_select(s, wl->idx) == 0) {
148 			server_redraw_session(s);
149 		}
150 		x -= wl->status_width + 1;
151 	}
152 }
153 
154 /* Draw status for client on the last lines of given context. */
155 int
156 status_redraw(struct client *c)
157 {
158 	struct screen_write_ctx	ctx;
159 	struct session	       *s = c->session;
160 	struct winlink	       *wl;
161 	struct screen		old_status, window_list;
162 	struct grid_cell	stdgc, lgc, rgc, gc;
163 	struct options	       *oo;
164 	time_t			t;
165 	char		       *left, *right, *sep;
166 	u_int			offset, needed;
167 	u_int			wlstart, wlwidth, wlavailable, wloffset, wlsize;
168 	size_t			llen, rlen, seplen;
169 	int			larrow, rarrow, utf8flag;
170 
171 	/* No status line? */
172 	if (c->tty.sy == 0 || !options_get_number(&s->options, "status"))
173 		return (1);
174 	left = right = NULL;
175 	larrow = rarrow = 0;
176 
177 	/* Update status timer. */
178 	if (gettimeofday(&c->status_timer, NULL) != 0)
179 		fatal("gettimeofday failed");
180 	t = c->status_timer.tv_sec;
181 
182 	/* Set up default colour. */
183 	memcpy(&stdgc, &grid_default_cell, sizeof gc);
184 	colour_set_fg(&stdgc, options_get_number(&s->options, "status-fg"));
185 	colour_set_bg(&stdgc, options_get_number(&s->options, "status-bg"));
186 	stdgc.attr |= options_get_number(&s->options, "status-attr");
187 
188 	/* Create the target screen. */
189 	memcpy(&old_status, &c->status, sizeof old_status);
190 	screen_init(&c->status, c->tty.sx, 1, 0);
191 	screen_write_start(&ctx, NULL, &c->status);
192 	for (offset = 0; offset < c->tty.sx; offset++)
193 		screen_write_putc(&ctx, &stdgc, ' ');
194 	screen_write_stop(&ctx);
195 
196 	/* If the height is one line, blank status line. */
197 	if (c->tty.sy <= 1)
198 		goto out;
199 
200 	/* Get UTF-8 flag. */
201 	utf8flag = options_get_number(&s->options, "status-utf8");
202 
203 	/* Work out left and right strings. */
204 	memcpy(&lgc, &stdgc, sizeof lgc);
205 	left = status_redraw_get_left(c, t, utf8flag, &lgc, &llen);
206 	memcpy(&rgc, &stdgc, sizeof rgc);
207 	right = status_redraw_get_right(c, t, utf8flag, &rgc, &rlen);
208 
209 	/*
210 	 * Figure out how much space we have for the window list. If there
211 	 * isn't enough space, just show a blank status line.
212 	 */
213 	needed = 0;
214 	if (llen != 0)
215 		needed += llen + 1;
216 	if (rlen != 0)
217 		needed += rlen + 1;
218 	if (c->tty.sx == 0 || c->tty.sx <= needed)
219 		goto out;
220 	wlavailable = c->tty.sx - needed;
221 
222 	/* Calculate the total size needed for the window list. */
223 	wlstart = wloffset = wlwidth = 0;
224 	RB_FOREACH(wl, winlinks, &s->windows) {
225 		free(wl->status_text);
226 		memcpy(&wl->status_cell, &stdgc, sizeof wl->status_cell);
227 		wl->status_text = status_print(c, wl, t, &wl->status_cell);
228 		wl->status_width =
229 		    screen_write_cstrlen(utf8flag, "%s", wl->status_text);
230 
231 		if (wl == s->curw)
232 			wloffset = wlwidth;
233 
234 		oo = &wl->window->options;
235 		sep = options_get_string(oo, "window-status-separator");
236 		seplen = screen_write_strlen(utf8flag, "%s", sep);
237 		wlwidth += wl->status_width + seplen;
238 	}
239 
240 	/* Create a new screen for the window list. */
241 	screen_init(&window_list, wlwidth, 1, 0);
242 
243 	/* And draw the window list into it. */
244 	screen_write_start(&ctx, NULL, &window_list);
245 	RB_FOREACH(wl, winlinks, &s->windows) {
246 		screen_write_cnputs(&ctx,
247 		    -1, &wl->status_cell, utf8flag, "%s", wl->status_text);
248 
249 		oo = &wl->window->options;
250 		sep = options_get_string(oo, "window-status-separator");
251 		screen_write_nputs(&ctx, -1, &stdgc, utf8flag, "%s", sep);
252 	}
253 	screen_write_stop(&ctx);
254 
255 	/* If there is enough space for the total width, skip to draw now. */
256 	if (wlwidth <= wlavailable)
257 		goto draw;
258 
259 	/* Find size of current window text. */
260 	wlsize = s->curw->status_width;
261 
262 	/*
263 	 * If the current window is already on screen, good to draw from the
264 	 * start and just leave off the end.
265 	 */
266 	if (wloffset + wlsize < wlavailable) {
267 		if (wlavailable > 0) {
268 			rarrow = 1;
269 			wlavailable--;
270 		}
271 		wlwidth = wlavailable;
272 	} else {
273 		/*
274 		 * Work out how many characters we need to omit from the
275 		 * start. There are wlavailable characters to fill, and
276 		 * wloffset + wlsize must be the last. So, the start character
277 		 * is wloffset + wlsize - wlavailable.
278 		 */
279 		if (wlavailable > 0) {
280 			larrow = 1;
281 			wlavailable--;
282 		}
283 
284 		wlstart = wloffset + wlsize - wlavailable;
285 		if (wlavailable > 0 && wlwidth > wlstart + wlavailable + 1) {
286 			rarrow = 1;
287 			wlstart++;
288 			wlavailable--;
289 		}
290 		wlwidth = wlavailable;
291 	}
292 
293 	/* Bail if anything is now too small too. */
294 	if (wlwidth == 0 || wlavailable == 0) {
295 		screen_free(&window_list);
296 		goto out;
297 	}
298 
299 	/*
300 	 * Now the start position is known, work out the state of the left and
301 	 * right arrows.
302 	 */
303 	offset = 0;
304 	RB_FOREACH(wl, winlinks, &s->windows) {
305 		if (wl->flags & WINLINK_ALERTFLAGS &&
306 		    larrow == 1 && offset < wlstart)
307 			larrow = -1;
308 
309 		offset += wl->status_width;
310 
311 		if (wl->flags & WINLINK_ALERTFLAGS &&
312 		    rarrow == 1 && offset > wlstart + wlwidth)
313 			rarrow = -1;
314 	}
315 
316 draw:
317 	/* Begin drawing. */
318 	screen_write_start(&ctx, NULL, &c->status);
319 
320 	/* Draw the left string and arrow. */
321 	screen_write_cursormove(&ctx, 0, 0);
322 	if (llen != 0) {
323 		screen_write_cnputs(&ctx, llen, &lgc, utf8flag, "%s", left);
324 		screen_write_putc(&ctx, &stdgc, ' ');
325 	}
326 	if (larrow != 0) {
327 		memcpy(&gc, &stdgc, sizeof gc);
328 		if (larrow == -1)
329 			gc.attr ^= GRID_ATTR_REVERSE;
330 		screen_write_putc(&ctx, &gc, '<');
331 	}
332 
333 	/* Draw the right string and arrow. */
334 	if (rarrow != 0) {
335 		screen_write_cursormove(&ctx, c->tty.sx - rlen - 2, 0);
336 		memcpy(&gc, &stdgc, sizeof gc);
337 		if (rarrow == -1)
338 			gc.attr ^= GRID_ATTR_REVERSE;
339 		screen_write_putc(&ctx, &gc, '>');
340 	} else
341 		screen_write_cursormove(&ctx, c->tty.sx - rlen - 1, 0);
342 	if (rlen != 0) {
343 		screen_write_putc(&ctx, &stdgc, ' ');
344 		screen_write_cnputs(&ctx, rlen, &rgc, utf8flag, "%s", right);
345 	}
346 
347 	/* Figure out the offset for the window list. */
348 	if (llen != 0)
349 		wloffset = llen + 1;
350 	else
351 		wloffset = 0;
352 	if (wlwidth < wlavailable) {
353 		switch (options_get_number(&s->options, "status-justify")) {
354 		case 1:	/* centered */
355 			wloffset += (wlavailable - wlwidth) / 2;
356 			break;
357 		case 2:	/* right */
358 			wloffset += (wlavailable - wlwidth);
359 			break;
360 		}
361 	}
362 	if (larrow != 0)
363 		wloffset++;
364 
365 	/* Copy the window list. */
366 	c->wlmouse = -wloffset + wlstart;
367 	screen_write_cursormove(&ctx, wloffset, 0);
368 	screen_write_copy(&ctx, &window_list, wlstart, 0, wlwidth, 1);
369 	screen_free(&window_list);
370 
371 	screen_write_stop(&ctx);
372 
373 out:
374 	free(left);
375 	free(right);
376 
377 	if (grid_compare(c->status.grid, old_status.grid) == 0) {
378 		screen_free(&old_status);
379 		return (0);
380 	}
381 	screen_free(&old_status);
382 	return (1);
383 }
384 
385 /* Replace a single special sequence (prefixed by #). */
386 void
387 status_replace1(struct client *c, struct session *s, struct winlink *wl,
388     struct window_pane *wp, char **iptr, char **optr, char *out,
389     size_t outsize, int jobsflag)
390 {
391 	char	ch, tmp[256], *ptr, *endptr, *freeptr;
392 	size_t	ptrlen;
393 	long	limit;
394 	u_int	idx;
395 
396 	if (s == NULL)
397 		s = c->session;
398 	if (wl == NULL)
399 		wl = s->curw;
400 	if (wp == NULL)
401 		wp = wl->window->active;
402 
403 	errno = 0;
404 	limit = strtol(*iptr, &endptr, 10);
405 	if ((limit == 0 && errno != EINVAL) ||
406 	    (limit == LONG_MIN && errno != ERANGE) ||
407 	    (limit == LONG_MAX && errno != ERANGE) ||
408 	    limit != 0)
409 		*iptr = endptr;
410 	if (limit <= 0)
411 		limit = LONG_MAX;
412 
413 	freeptr = NULL;
414 
415 	switch (*(*iptr)++) {
416 	case '(':
417 		if (!jobsflag) {
418 			ch = ')';
419 			goto skip_to;
420 		}
421 		if ((ptr = status_find_job(c, iptr)) == NULL)
422 			return;
423 		goto do_replace;
424 	case 'D':
425 		xsnprintf(tmp, sizeof tmp, "%%%u", wp->id);
426 		ptr = tmp;
427 		goto do_replace;
428 	case 'H':
429 		if (gethostname(tmp, sizeof tmp) != 0)
430 			fatal("gethostname failed");
431 		ptr = tmp;
432 		goto do_replace;
433 	case 'h':
434 		if (gethostname(tmp, sizeof tmp) != 0)
435 			fatal("gethostname failed");
436 		if ((ptr = strchr(tmp, '.')) != NULL)
437 			*ptr = '\0';
438 		ptr = tmp;
439 		goto do_replace;
440 	case 'I':
441 		xsnprintf(tmp, sizeof tmp, "%d", wl->idx);
442 		ptr = tmp;
443 		goto do_replace;
444 	case 'P':
445 		if (window_pane_index(wp, &idx) != 0)
446 			fatalx("index not found");
447 		xsnprintf(
448 		    tmp, sizeof tmp, "%u", idx);
449 		ptr = tmp;
450 		goto do_replace;
451 	case 'S':
452 		ptr = s->name;
453 		goto do_replace;
454 	case 'T':
455 		ptr = wp->base.title;
456 		goto do_replace;
457 	case 'W':
458 		ptr = wl->window->name;
459 		goto do_replace;
460 	case 'F':
461 		ptr = window_printable_flags(s, wl);
462 		freeptr = ptr;
463 		goto do_replace;
464 	case '[':
465 		/*
466 		 * Embedded style, handled at display time. Leave present and
467 		 * skip input until ].
468 		 */
469 		ch = ']';
470 		goto skip_to;
471 	case '#':
472 		*(*optr)++ = '#';
473 		break;
474 	}
475 
476 	return;
477 
478 do_replace:
479 	ptrlen = strlen(ptr);
480 	if ((size_t) limit < ptrlen)
481 		ptrlen = limit;
482 
483 	if (*optr + ptrlen >= out + outsize - 1)
484 		goto out;
485 	while (ptrlen > 0 && *ptr != '\0') {
486 		*(*optr)++ = *ptr++;
487 		ptrlen--;
488 	}
489 
490 out:
491 	free(freeptr);
492 	return;
493 
494 skip_to:
495 	*(*optr)++ = '#';
496 
497 	(*iptr)--;	/* include ch */
498 	while (**iptr != ch && **iptr != '\0') {
499 		if (*optr >=  out + outsize - 1)
500 			break;
501 		*(*optr)++ = *(*iptr)++;
502 	}
503 }
504 
505 /* Replace special sequences in fmt. */
506 char *
507 status_replace(struct client *c, struct session *s, struct winlink *wl,
508     struct window_pane *wp, const char *fmt, time_t t, int jobsflag)
509 {
510 	static char	out[BUFSIZ];
511 	char		in[BUFSIZ], ch, *iptr, *optr;
512 	size_t		len;
513 
514 	if (fmt == NULL)
515 		return (xstrdup(""));
516 
517 	len = strftime(in, sizeof in, fmt, localtime(&t));
518 	in[len] = '\0';
519 
520 	iptr = in;
521 	optr = out;
522 
523 	while (*iptr != '\0') {
524 		if (optr >= out + (sizeof out) - 1)
525 			break;
526 		ch = *iptr++;
527 
528 		if (ch != '#' || *iptr == '\0') {
529 			*optr++ = ch;
530 			continue;
531 		}
532 		status_replace1(
533 		    c, s, wl, wp, &iptr, &optr, out, sizeof out, jobsflag);
534 	}
535 	*optr = '\0';
536 
537 	return (xstrdup(out));
538 }
539 
540 /* Figure out job name and get its result, starting it off if necessary. */
541 char *
542 status_find_job(struct client *c, char **iptr)
543 {
544 	struct status_out	*so, so_find;
545 	char   			*cmd;
546 	int			 lastesc;
547 	size_t			 len;
548 
549 	if (**iptr == '\0')
550 		return (NULL);
551 	if (**iptr == ')') {		/* no command given */
552 		(*iptr)++;
553 		return (NULL);
554 	}
555 
556 	cmd = xmalloc(strlen(*iptr) + 1);
557 	len = 0;
558 
559 	lastesc = 0;
560 	for (; **iptr != '\0'; (*iptr)++) {
561 		if (!lastesc && **iptr == ')')
562 			break;		/* unescaped ) is the end */
563 		if (!lastesc && **iptr == '\\') {
564 			lastesc = 1;
565 			continue;	/* skip \ if not escaped */
566 		}
567 		lastesc = 0;
568 		cmd[len++] = **iptr;
569 	}
570 	if (**iptr == '\0')		/* no terminating ) */ {
571 		free(cmd);
572 		return (NULL);
573 	}
574 	(*iptr)++;			/* skip final ) */
575 	cmd[len] = '\0';
576 
577 	/* First try in the new tree. */
578 	so_find.cmd = cmd;
579 	so = RB_FIND(status_out_tree, &c->status_new, &so_find);
580 	if (so != NULL && so->out != NULL) {
581 		free(cmd);
582 		return (so->out);
583 	}
584 
585 	/* If not found at all, start the job and add to the tree. */
586 	if (so == NULL) {
587 		job_run(cmd, status_job_callback, status_job_free, c);
588 		c->references++;
589 
590 		so = xmalloc(sizeof *so);
591 		so->cmd = xstrdup(cmd);
592 		so->out = NULL;
593 		RB_INSERT(status_out_tree, &c->status_new, so);
594 	}
595 
596 	/* Lookup in the old tree. */
597 	so_find.cmd = cmd;
598 	so = RB_FIND(status_out_tree, &c->status_old, &so_find);
599 	free(cmd);
600 	if (so != NULL)
601 		return (so->out);
602 	return (NULL);
603 }
604 
605 /* Free job tree. */
606 void
607 status_free_jobs(struct status_out_tree *sotree)
608 {
609 	struct status_out	*so, *so_next;
610 
611 	so_next = RB_MIN(status_out_tree, sotree);
612 	while (so_next != NULL) {
613 		so = so_next;
614 		so_next = RB_NEXT(status_out_tree, sotree, so);
615 
616 		RB_REMOVE(status_out_tree, sotree, so);
617 		free(so->out);
618 		free(so->cmd);
619 		free(so);
620 	}
621 }
622 
623 /* Update jobs on status interval. */
624 void
625 status_update_jobs(struct client *c)
626 {
627 	/* Free the old tree. */
628 	status_free_jobs(&c->status_old);
629 
630 	/* Move the new to old. */
631 	memcpy(&c->status_old, &c->status_new, sizeof c->status_old);
632 	RB_INIT(&c->status_new);
633 }
634 
635 /* Free status job. */
636 void
637 status_job_free(void *data)
638 {
639 	struct client	*c = data;
640 
641 	c->references--;
642 }
643 
644 /* Job has finished: save its result. */
645 void
646 status_job_callback(struct job *job)
647 {
648 	struct client		*c = job->data;
649 	struct status_out	*so, so_find;
650 	char			*line, *buf;
651 	size_t			 len;
652 
653 	if (c->flags & CLIENT_DEAD)
654 		return;
655 
656 	so_find.cmd = job->cmd;
657 	so = RB_FIND(status_out_tree, &c->status_new, &so_find);
658 	if (so == NULL || so->out != NULL)
659 		return;
660 
661 	buf = NULL;
662 	if ((line = evbuffer_readline(job->event->input)) == NULL) {
663 		len = EVBUFFER_LENGTH(job->event->input);
664 		buf = xmalloc(len + 1);
665 		if (len != 0)
666 			memcpy(buf, EVBUFFER_DATA(job->event->input), len);
667 		buf[len] = '\0';
668 	} else
669 		buf = xstrdup(line);
670 
671 	so->out = buf;
672 	server_status_client(c);
673 }
674 
675 /* Return winlink status line entry and adjust gc as necessary. */
676 char *
677 status_print(
678     struct client *c, struct winlink *wl, time_t t, struct grid_cell *gc)
679 {
680 	struct options	*oo = &wl->window->options;
681 	struct session	*s = c->session;
682 	const char	*fmt;
683 	char   		*text;
684 	int		 fg, bg, attr;
685 
686 	fg = options_get_number(oo, "window-status-fg");
687 	if (fg != 8)
688 		colour_set_fg(gc, fg);
689 	bg = options_get_number(oo, "window-status-bg");
690 	if (bg != 8)
691 		colour_set_bg(gc, bg);
692 	attr = options_get_number(oo, "window-status-attr");
693 	if (attr != 0)
694 		gc->attr = attr;
695 	fmt = options_get_string(oo, "window-status-format");
696 	if (wl == s->curw) {
697 		fg = options_get_number(oo, "window-status-current-fg");
698 		if (fg != 8)
699 			colour_set_fg(gc, fg);
700 		bg = options_get_number(oo, "window-status-current-bg");
701 		if (bg != 8)
702 			colour_set_bg(gc, bg);
703 		attr = options_get_number(oo, "window-status-current-attr");
704 		if (attr != 0)
705 			gc->attr = attr;
706 		fmt = options_get_string(oo, "window-status-current-format");
707 	}
708 
709 	if (wl->flags & WINLINK_BELL) {
710 		fg = options_get_number(oo, "window-status-bell-fg");
711 		if (fg != 8)
712 			colour_set_fg(gc, fg);
713 		bg = options_get_number(oo, "window-status-bell-bg");
714 		if (bg != 8)
715 			colour_set_bg(gc, bg);
716 		attr = options_get_number(oo, "window-status-bell-attr");
717 		if (attr != 0)
718 			gc->attr = attr;
719 	} else if (wl->flags & WINLINK_CONTENT) {
720 		fg = options_get_number(oo, "window-status-content-fg");
721 		if (fg != 8)
722 			colour_set_fg(gc, fg);
723 		bg = options_get_number(oo, "window-status-content-bg");
724 		if (bg != 8)
725 			colour_set_bg(gc, bg);
726 		attr = options_get_number(oo, "window-status-content-attr");
727 		if (attr != 0)
728 			gc->attr = attr;
729 	} else if (wl->flags & (WINLINK_ACTIVITY|WINLINK_SILENCE)) {
730 		fg = options_get_number(oo, "window-status-activity-fg");
731 		if (fg != 8)
732 			colour_set_fg(gc, fg);
733 		bg = options_get_number(oo, "window-status-activity-bg");
734 		if (bg != 8)
735 			colour_set_bg(gc, bg);
736 		attr = options_get_number(oo, "window-status-activity-attr");
737 		if (attr != 0)
738 			gc->attr = attr;
739 	}
740 
741 	text = status_replace(c, NULL, wl, NULL, fmt, t, 1);
742 	return (text);
743 }
744 
745 /* Set a status line message. */
746 void printflike2
747 status_message_set(struct client *c, const char *fmt, ...)
748 {
749 	struct timeval		 tv;
750 	struct session		*s = c->session;
751 	struct message_entry	*msg;
752 	va_list			 ap;
753 	int			 delay;
754 	u_int			 i, limit;
755 
756 	status_prompt_clear(c);
757 	status_message_clear(c);
758 
759 	va_start(ap, fmt);
760 	xvasprintf(&c->message_string, fmt, ap);
761 	va_end(ap);
762 
763 	ARRAY_EXPAND(&c->message_log, 1);
764 	msg = &ARRAY_LAST(&c->message_log);
765 	msg->msg_time = time(NULL);
766 	msg->msg = xstrdup(c->message_string);
767 
768 	if (s == NULL)
769 		limit = 0;
770 	else
771 		limit = options_get_number(&s->options, "message-limit");
772 	if (ARRAY_LENGTH(&c->message_log) > limit) {
773 		limit = ARRAY_LENGTH(&c->message_log) - limit;
774 		for (i = 0; i < limit; i++) {
775 			msg = &ARRAY_FIRST(&c->message_log);
776 			free(msg->msg);
777 			ARRAY_REMOVE(&c->message_log, 0);
778 		}
779 	}
780 
781 	delay = options_get_number(&c->session->options, "display-time");
782 	tv.tv_sec = delay / 1000;
783 	tv.tv_usec = (delay % 1000) * 1000L;
784 
785 	if (event_initialized (&c->message_timer))
786 		evtimer_del(&c->message_timer);
787 	evtimer_set(&c->message_timer, status_message_callback, c);
788 	evtimer_add(&c->message_timer, &tv);
789 
790 	c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
791 	c->flags |= CLIENT_STATUS;
792 }
793 
794 /* Clear status line message. */
795 void
796 status_message_clear(struct client *c)
797 {
798 	if (c->message_string == NULL)
799 		return;
800 
801 	free(c->message_string);
802 	c->message_string = NULL;
803 
804 	c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
805 	c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
806 
807 	screen_reinit(&c->status);
808 }
809 
810 /* Clear status line message after timer expires. */
811 /* ARGSUSED */
812 void
813 status_message_callback(unused int fd, unused short event, void *data)
814 {
815 	struct client	*c = data;
816 
817 	status_message_clear(c);
818 }
819 
820 /* Draw client message on status line of present else on last line. */
821 int
822 status_message_redraw(struct client *c)
823 {
824 	struct screen_write_ctx		ctx;
825 	struct session		       *s = c->session;
826 	struct screen		        old_status;
827 	size_t			        len;
828 	struct grid_cell		gc;
829 	int				utf8flag;
830 
831 	if (c->tty.sx == 0 || c->tty.sy == 0)
832 		return (0);
833 	memcpy(&old_status, &c->status, sizeof old_status);
834 	screen_init(&c->status, c->tty.sx, 1, 0);
835 
836 	utf8flag = options_get_number(&s->options, "status-utf8");
837 
838 	len = screen_write_strlen(utf8flag, "%s", c->message_string);
839 	if (len > c->tty.sx)
840 		len = c->tty.sx;
841 
842 	memcpy(&gc, &grid_default_cell, sizeof gc);
843 	colour_set_fg(&gc, options_get_number(&s->options, "message-fg"));
844 	colour_set_bg(&gc, options_get_number(&s->options, "message-bg"));
845 	gc.attr |= options_get_number(&s->options, "message-attr");
846 
847 	screen_write_start(&ctx, NULL, &c->status);
848 
849 	screen_write_cursormove(&ctx, 0, 0);
850 	screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->message_string);
851 	for (; len < c->tty.sx; len++)
852 		screen_write_putc(&ctx, &gc, ' ');
853 
854 	screen_write_stop(&ctx);
855 
856 	if (grid_compare(c->status.grid, old_status.grid) == 0) {
857 		screen_free(&old_status);
858 		return (0);
859 	}
860 	screen_free(&old_status);
861 	return (1);
862 }
863 
864 /* Enable status line prompt. */
865 void
866 status_prompt_set(struct client *c, const char *msg, const char *input,
867     int (*callbackfn)(void *, const char *), void (*freefn)(void *),
868     void *data, int flags)
869 {
870 	int	keys;
871 
872 	status_message_clear(c);
873 	status_prompt_clear(c);
874 
875 	c->prompt_string = status_replace(c, NULL, NULL, NULL, msg,
876 	    time(NULL), 0);
877 
878 	c->prompt_buffer = status_replace(c, NULL, NULL, NULL, input,
879 	    time(NULL), 0);
880 	c->prompt_index = strlen(c->prompt_buffer);
881 
882 	c->prompt_callbackfn = callbackfn;
883 	c->prompt_freefn = freefn;
884 	c->prompt_data = data;
885 
886 	c->prompt_hindex = 0;
887 
888 	c->prompt_flags = flags;
889 
890 	keys = options_get_number(&c->session->options, "status-keys");
891 	if (keys == MODEKEY_EMACS)
892 		mode_key_init(&c->prompt_mdata, &mode_key_tree_emacs_edit);
893 	else
894 		mode_key_init(&c->prompt_mdata, &mode_key_tree_vi_edit);
895 
896 	c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
897 	c->flags |= CLIENT_STATUS;
898 }
899 
900 /* Remove status line prompt. */
901 void
902 status_prompt_clear(struct client *c)
903 {
904 	if (c->prompt_string == NULL)
905 		return;
906 
907 	if (c->prompt_freefn != NULL && c->prompt_data != NULL)
908 		c->prompt_freefn(c->prompt_data);
909 
910 	free(c->prompt_string);
911 	c->prompt_string = NULL;
912 
913 	free(c->prompt_buffer);
914 	c->prompt_buffer = NULL;
915 
916 	c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
917 	c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
918 
919 	screen_reinit(&c->status);
920 }
921 
922 /* Update status line prompt with a new prompt string. */
923 void
924 status_prompt_update(struct client *c, const char *msg, const char *input)
925 {
926 	free(c->prompt_string);
927 	c->prompt_string = status_replace(c, NULL, NULL, NULL, msg,
928 	    time(NULL), 0);
929 
930 	free(c->prompt_buffer);
931 	c->prompt_buffer = status_replace(c, NULL, NULL, NULL, input,
932 	    time(NULL), 0);
933 	c->prompt_index = strlen(c->prompt_buffer);
934 
935 	c->prompt_hindex = 0;
936 
937 	c->flags |= CLIENT_STATUS;
938 }
939 
940 /* Draw client prompt on status line of present else on last line. */
941 int
942 status_prompt_redraw(struct client *c)
943 {
944 	struct screen_write_ctx		ctx;
945 	struct session		       *s = c->session;
946 	struct screen		        old_status;
947 	size_t			        i, size, left, len, off;
948 	struct grid_cell		gc, *gcp;
949 	int				utf8flag;
950 
951 	if (c->tty.sx == 0 || c->tty.sy == 0)
952 		return (0);
953 	memcpy(&old_status, &c->status, sizeof old_status);
954 	screen_init(&c->status, c->tty.sx, 1, 0);
955 
956 	utf8flag = options_get_number(&s->options, "status-utf8");
957 
958 	len = screen_write_strlen(utf8flag, "%s", c->prompt_string);
959 	if (len > c->tty.sx)
960 		len = c->tty.sx;
961 	off = 0;
962 
963 	memcpy(&gc, &grid_default_cell, sizeof gc);
964 	/* Change colours for command mode. */
965 	if (c->prompt_mdata.mode == 1) {
966 		colour_set_fg(&gc, options_get_number(&s->options, "message-command-fg"));
967 		colour_set_bg(&gc, options_get_number(&s->options, "message-command-bg"));
968 		gc.attr |= options_get_number(&s->options, "message-command-attr");
969 	} else {
970 		colour_set_fg(&gc, options_get_number(&s->options, "message-fg"));
971 		colour_set_bg(&gc, options_get_number(&s->options, "message-bg"));
972 		gc.attr |= options_get_number(&s->options, "message-attr");
973 	}
974 
975 	screen_write_start(&ctx, NULL, &c->status);
976 
977 	screen_write_cursormove(&ctx, 0, 0);
978 	screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->prompt_string);
979 
980 	left = c->tty.sx - len;
981 	if (left != 0) {
982 		size = screen_write_strlen(utf8flag, "%s", c->prompt_buffer);
983 		if (c->prompt_index >= left) {
984 			off = c->prompt_index - left + 1;
985 			if (c->prompt_index == size)
986 				left--;
987 			size = left;
988 		}
989 		screen_write_nputs(
990 		    &ctx, left, &gc, utf8flag, "%s", c->prompt_buffer + off);
991 
992 		for (i = len + size; i < c->tty.sx; i++)
993 			screen_write_putc(&ctx, &gc, ' ');
994 	}
995 
996 	screen_write_stop(&ctx);
997 
998 	/* Apply fake cursor. */
999 	off = len + c->prompt_index - off;
1000 	gcp = grid_view_get_cell(c->status.grid, off, 0);
1001 	gcp->attr ^= GRID_ATTR_REVERSE;
1002 
1003 	if (grid_compare(c->status.grid, old_status.grid) == 0) {
1004 		screen_free(&old_status);
1005 		return (0);
1006 	}
1007 	screen_free(&old_status);
1008 	return (1);
1009 }
1010 
1011 /* Handle keys in prompt. */
1012 void
1013 status_prompt_key(struct client *c, int key)
1014 {
1015 	struct session		*sess = c->session;
1016 	struct options		*oo = &sess->options;
1017 	struct paste_buffer	*pb;
1018 	char			*s, *first, *last, word[64], swapc;
1019 	const char		*histstr;
1020 	const char		*wsep = NULL;
1021 	u_char			 ch;
1022 	size_t			 size, n, off, idx;
1023 
1024 	size = strlen(c->prompt_buffer);
1025 	switch (mode_key_lookup(&c->prompt_mdata, key)) {
1026 	case MODEKEYEDIT_CURSORLEFT:
1027 		if (c->prompt_index > 0) {
1028 			c->prompt_index--;
1029 			c->flags |= CLIENT_STATUS;
1030 		}
1031 		break;
1032 	case MODEKEYEDIT_SWITCHMODE:
1033 		c->flags |= CLIENT_STATUS;
1034 		break;
1035 	case MODEKEYEDIT_SWITCHMODEAPPEND:
1036 		c->flags |= CLIENT_STATUS;
1037 		/* FALLTHROUGH */
1038 	case MODEKEYEDIT_CURSORRIGHT:
1039 		if (c->prompt_index < size) {
1040 			c->prompt_index++;
1041 			c->flags |= CLIENT_STATUS;
1042 		}
1043 		break;
1044 	case MODEKEYEDIT_SWITCHMODEBEGINLINE:
1045 		c->flags |= CLIENT_STATUS;
1046 		/* FALLTHROUGH */
1047 	case MODEKEYEDIT_STARTOFLINE:
1048 		if (c->prompt_index != 0) {
1049 			c->prompt_index = 0;
1050 			c->flags |= CLIENT_STATUS;
1051 		}
1052 		break;
1053 	case MODEKEYEDIT_SWITCHMODEAPPENDLINE:
1054 		c->flags |= CLIENT_STATUS;
1055 		/* FALLTHROUGH */
1056 	case MODEKEYEDIT_ENDOFLINE:
1057 		if (c->prompt_index != size) {
1058 			c->prompt_index = size;
1059 			c->flags |= CLIENT_STATUS;
1060 		}
1061 		break;
1062 	case MODEKEYEDIT_COMPLETE:
1063 		if (*c->prompt_buffer == '\0')
1064 			break;
1065 
1066 		idx = c->prompt_index;
1067 		if (idx != 0)
1068 			idx--;
1069 
1070 		/* Find the word we are in. */
1071 		first = c->prompt_buffer + idx;
1072 		while (first > c->prompt_buffer && *first != ' ')
1073 			first--;
1074 		while (*first == ' ')
1075 			first++;
1076 		last = c->prompt_buffer + idx;
1077 		while (*last != '\0' && *last != ' ')
1078 			last++;
1079 		while (*last == ' ')
1080 			last--;
1081 		if (*last != '\0')
1082 			last++;
1083 		if (last <= first ||
1084 		    ((size_t) (last - first)) > (sizeof word) - 1)
1085 			break;
1086 		memcpy(word, first, last - first);
1087 		word[last - first] = '\0';
1088 
1089 		/* And try to complete it. */
1090 		if ((s = status_prompt_complete(word)) == NULL)
1091 			break;
1092 
1093 		/* Trim out word. */
1094 		n = size - (last - c->prompt_buffer) + 1; /* with \0 */
1095 		memmove(first, last, n);
1096 		size -= last - first;
1097 
1098 		/* Insert the new word. */
1099 		size += strlen(s);
1100 		off = first - c->prompt_buffer;
1101 		c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 1);
1102 		first = c->prompt_buffer + off;
1103 		memmove(first + strlen(s), first, n);
1104 		memcpy(first, s, strlen(s));
1105 
1106 		c->prompt_index = (first - c->prompt_buffer) + strlen(s);
1107 		free(s);
1108 
1109 		c->flags |= CLIENT_STATUS;
1110 		break;
1111 	case MODEKEYEDIT_BACKSPACE:
1112 		if (c->prompt_index != 0) {
1113 			if (c->prompt_index == size)
1114 				c->prompt_buffer[--c->prompt_index] = '\0';
1115 			else {
1116 				memmove(c->prompt_buffer + c->prompt_index - 1,
1117 				    c->prompt_buffer + c->prompt_index,
1118 				    size + 1 - c->prompt_index);
1119 				c->prompt_index--;
1120 			}
1121 			c->flags |= CLIENT_STATUS;
1122 		}
1123 		break;
1124 	case MODEKEYEDIT_DELETE:
1125 		if (c->prompt_index != size) {
1126 			memmove(c->prompt_buffer + c->prompt_index,
1127 			    c->prompt_buffer + c->prompt_index + 1,
1128 			    size + 1 - c->prompt_index);
1129 			c->flags |= CLIENT_STATUS;
1130 		}
1131 		break;
1132 	case MODEKEYEDIT_DELETELINE:
1133 		*c->prompt_buffer = '\0';
1134 		c->prompt_index = 0;
1135 		c->flags |= CLIENT_STATUS;
1136 		break;
1137 	case MODEKEYEDIT_DELETETOENDOFLINE:
1138 		if (c->prompt_index < size) {
1139 			c->prompt_buffer[c->prompt_index] = '\0';
1140 			c->flags |= CLIENT_STATUS;
1141 		}
1142 		break;
1143 	case MODEKEYEDIT_DELETEWORD:
1144 		wsep = options_get_string(oo, "word-separators");
1145 		idx = c->prompt_index;
1146 
1147 		/* Find a non-separator. */
1148 		while (idx != 0) {
1149 			idx--;
1150 			if (!strchr(wsep, c->prompt_buffer[idx]))
1151 				break;
1152 		}
1153 
1154 		/* Find the separator at the beginning of the word. */
1155 		while (idx != 0) {
1156 			idx--;
1157 			if (strchr(wsep, c->prompt_buffer[idx])) {
1158 				/* Go back to the word. */
1159 				idx++;
1160 				break;
1161 			}
1162 		}
1163 
1164 		memmove(c->prompt_buffer + idx,
1165 		    c->prompt_buffer + c->prompt_index,
1166 		    size + 1 - c->prompt_index);
1167 		memset(c->prompt_buffer + size - (c->prompt_index - idx),
1168 		    '\0', c->prompt_index - idx);
1169 		c->prompt_index = idx;
1170 		c->flags |= CLIENT_STATUS;
1171 		break;
1172 	case MODEKEYEDIT_NEXTSPACE:
1173 		wsep = " ";
1174 		/* FALLTHROUGH */
1175 	case MODEKEYEDIT_NEXTWORD:
1176 		if (wsep == NULL)
1177 			wsep = options_get_string(oo, "word-separators");
1178 
1179 		/* Find a separator. */
1180 		while (c->prompt_index != size) {
1181 			c->prompt_index++;
1182 			if (strchr(wsep, c->prompt_buffer[c->prompt_index]))
1183 				break;
1184 		}
1185 
1186 		/* Find the word right after the separation. */
1187 		while (c->prompt_index != size) {
1188 			c->prompt_index++;
1189 			if (!strchr(wsep, c->prompt_buffer[c->prompt_index]))
1190 				break;
1191 		}
1192 
1193 		c->flags |= CLIENT_STATUS;
1194 		break;
1195 	case MODEKEYEDIT_NEXTSPACEEND:
1196 		wsep = " ";
1197 		/* FALLTHROUGH */
1198 	case MODEKEYEDIT_NEXTWORDEND:
1199 		if (wsep == NULL)
1200 			wsep = options_get_string(oo, "word-separators");
1201 
1202 		/* Find a word. */
1203 		while (c->prompt_index != size) {
1204 			c->prompt_index++;
1205 			if (!strchr(wsep, c->prompt_buffer[c->prompt_index]))
1206 				break;
1207 		}
1208 
1209 		/* Find the separator at the end of the word. */
1210 		while (c->prompt_index != size) {
1211 			c->prompt_index++;
1212 			if (strchr(wsep, c->prompt_buffer[c->prompt_index]))
1213 				break;
1214 		}
1215 
1216 		c->flags |= CLIENT_STATUS;
1217 		break;
1218 	case MODEKEYEDIT_PREVIOUSSPACE:
1219 		wsep = " ";
1220 		/* FALLTHROUGH */
1221 	case MODEKEYEDIT_PREVIOUSWORD:
1222 		if (wsep == NULL)
1223 			wsep = options_get_string(oo, "word-separators");
1224 
1225 		/* Find a non-separator. */
1226 		while (c->prompt_index != 0) {
1227 			c->prompt_index--;
1228 			if (!strchr(wsep, c->prompt_buffer[c->prompt_index]))
1229 				break;
1230 		}
1231 
1232 		/* Find the separator at the beginning of the word. */
1233 		while (c->prompt_index != 0) {
1234 			c->prompt_index--;
1235 			if (strchr(wsep, c->prompt_buffer[c->prompt_index])) {
1236 				/* Go back to the word. */
1237 				c->prompt_index++;
1238 				break;
1239 			}
1240 		}
1241 
1242 		c->flags |= CLIENT_STATUS;
1243 		break;
1244 	case MODEKEYEDIT_HISTORYUP:
1245 		histstr = status_prompt_up_history(&c->prompt_hindex);
1246 		if (histstr == NULL)
1247 			break;
1248 		free(c->prompt_buffer);
1249 		c->prompt_buffer = xstrdup(histstr);
1250 		c->prompt_index = strlen(c->prompt_buffer);
1251 		c->flags |= CLIENT_STATUS;
1252 		break;
1253 	case MODEKEYEDIT_HISTORYDOWN:
1254 		histstr = status_prompt_down_history(&c->prompt_hindex);
1255 		if (histstr == NULL)
1256 			break;
1257 		free(c->prompt_buffer);
1258 		c->prompt_buffer = xstrdup(histstr);
1259 		c->prompt_index = strlen(c->prompt_buffer);
1260 		c->flags |= CLIENT_STATUS;
1261 		break;
1262 	case MODEKEYEDIT_PASTE:
1263 		if ((pb = paste_get_top(&global_buffers)) == NULL)
1264 			break;
1265 		for (n = 0; n < pb->size; n++) {
1266 			ch = (u_char) pb->data[n];
1267 			if (ch < 32 || ch == 127)
1268 				break;
1269 		}
1270 
1271 		c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + n + 1);
1272 		if (c->prompt_index == size) {
1273 			memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
1274 			c->prompt_index += n;
1275 			c->prompt_buffer[c->prompt_index] = '\0';
1276 		} else {
1277 			memmove(c->prompt_buffer + c->prompt_index + n,
1278 			    c->prompt_buffer + c->prompt_index,
1279 			    size + 1 - c->prompt_index);
1280 			memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
1281 			c->prompt_index += n;
1282 		}
1283 
1284 		c->flags |= CLIENT_STATUS;
1285 		break;
1286 	case MODEKEYEDIT_TRANSPOSECHARS:
1287 		idx = c->prompt_index;
1288 		if (idx < size)
1289 			idx++;
1290 		if (idx >= 2) {
1291 			swapc = c->prompt_buffer[idx - 2];
1292 			c->prompt_buffer[idx - 2] = c->prompt_buffer[idx - 1];
1293 			c->prompt_buffer[idx - 1] = swapc;
1294 			c->prompt_index = idx;
1295 			c->flags |= CLIENT_STATUS;
1296 		}
1297 		break;
1298 	case MODEKEYEDIT_ENTER:
1299 		if (*c->prompt_buffer != '\0')
1300 			status_prompt_add_history(c->prompt_buffer);
1301 		if (c->prompt_callbackfn(c->prompt_data, c->prompt_buffer) == 0)
1302 			status_prompt_clear(c);
1303 		break;
1304 	case MODEKEYEDIT_CANCEL:
1305 		if (c->prompt_callbackfn(c->prompt_data, NULL) == 0)
1306 			status_prompt_clear(c);
1307 		break;
1308 	case MODEKEY_OTHER:
1309 		if ((key & 0xff00) != 0 || key < 32 || key == 127)
1310 			break;
1311 		c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 2);
1312 
1313 		if (c->prompt_index == size) {
1314 			c->prompt_buffer[c->prompt_index++] = key;
1315 			c->prompt_buffer[c->prompt_index] = '\0';
1316 		} else {
1317 			memmove(c->prompt_buffer + c->prompt_index + 1,
1318 			    c->prompt_buffer + c->prompt_index,
1319 			    size + 1 - c->prompt_index);
1320 			c->prompt_buffer[c->prompt_index++] = key;
1321 		}
1322 
1323 		if (c->prompt_flags & PROMPT_SINGLE) {
1324 			if (c->prompt_callbackfn(
1325 			    c->prompt_data, c->prompt_buffer) == 0)
1326 				status_prompt_clear(c);
1327 		}
1328 
1329 		c->flags |= CLIENT_STATUS;
1330 		break;
1331 	default:
1332 		break;
1333 	}
1334 }
1335 
1336 /* Get previous line from the history. */
1337 const char *
1338 status_prompt_up_history(u_int *idx)
1339 {
1340 	u_int size;
1341 
1342 	/*
1343 	 * History runs from 0 to size - 1.
1344 	 *
1345 	 * Index is from 0 to size. Zero is empty.
1346 	 */
1347 
1348 	size = ARRAY_LENGTH(&status_prompt_history);
1349 	if (size == 0 || *idx == size)
1350 		return (NULL);
1351 	(*idx)++;
1352 	return (ARRAY_ITEM(&status_prompt_history, size - *idx));
1353 }
1354 
1355 /* Get next line from the history. */
1356 const char *
1357 status_prompt_down_history(u_int *idx)
1358 {
1359 	u_int size;
1360 
1361 	size = ARRAY_LENGTH(&status_prompt_history);
1362 	if (size == 0 || *idx == 0)
1363 		return ("");
1364 	(*idx)--;
1365 	if (*idx == 0)
1366 		return ("");
1367 	return (ARRAY_ITEM(&status_prompt_history, size - *idx));
1368 }
1369 
1370 /* Add line to the history. */
1371 void
1372 status_prompt_add_history(const char *line)
1373 {
1374 	u_int size;
1375 
1376 	size = ARRAY_LENGTH(&status_prompt_history);
1377 	if (size > 0 && strcmp(ARRAY_LAST(&status_prompt_history), line) == 0)
1378 		return;
1379 
1380 	if (size == PROMPT_HISTORY) {
1381 		free(ARRAY_FIRST(&status_prompt_history));
1382 		ARRAY_REMOVE(&status_prompt_history, 0);
1383 	}
1384 
1385 	ARRAY_ADD(&status_prompt_history, xstrdup(line));
1386 }
1387 
1388 /* Complete word. */
1389 char *
1390 status_prompt_complete(const char *s)
1391 {
1392 	const struct cmd_entry 	  	       **cmdent;
1393 	const struct options_table_entry	*oe;
1394 	ARRAY_DECL(, const char *)		 list;
1395 	char					*prefix, *s2;
1396 	u_int					 i;
1397 	size_t				 	 j;
1398 
1399 	if (*s == '\0')
1400 		return (NULL);
1401 
1402 	/* First, build a list of all the possible matches. */
1403 	ARRAY_INIT(&list);
1404 	for (cmdent = cmd_table; *cmdent != NULL; cmdent++) {
1405 		if (strncmp((*cmdent)->name, s, strlen(s)) == 0)
1406 			ARRAY_ADD(&list, (*cmdent)->name);
1407 	}
1408 	for (oe = server_options_table; oe->name != NULL; oe++) {
1409 		if (strncmp(oe->name, s, strlen(s)) == 0)
1410 			ARRAY_ADD(&list, oe->name);
1411 	}
1412 	for (oe = session_options_table; oe->name != NULL; oe++) {
1413 		if (strncmp(oe->name, s, strlen(s)) == 0)
1414 			ARRAY_ADD(&list, oe->name);
1415 	}
1416 	for (oe = window_options_table; oe->name != NULL; oe++) {
1417 		if (strncmp(oe->name, s, strlen(s)) == 0)
1418 			ARRAY_ADD(&list, oe->name);
1419 	}
1420 
1421 	/* If none, bail now. */
1422 	if (ARRAY_LENGTH(&list) == 0) {
1423 		ARRAY_FREE(&list);
1424 		return (NULL);
1425 	}
1426 
1427 	/* If an exact match, return it, with a trailing space. */
1428 	if (ARRAY_LENGTH(&list) == 1) {
1429 		xasprintf(&s2, "%s ", ARRAY_FIRST(&list));
1430 		ARRAY_FREE(&list);
1431 		return (s2);
1432 	}
1433 
1434 	/* Now loop through the list and find the longest common prefix. */
1435 	prefix = xstrdup(ARRAY_FIRST(&list));
1436 	for (i = 1; i < ARRAY_LENGTH(&list); i++) {
1437 		s = ARRAY_ITEM(&list, i);
1438 
1439 		j = strlen(s);
1440 		if (j > strlen(prefix))
1441 			j = strlen(prefix);
1442 		for (; j > 0; j--) {
1443 			if (prefix[j - 1] != s[j - 1])
1444 				prefix[j - 1] = '\0';
1445 		}
1446 	}
1447 
1448 	ARRAY_FREE(&list);
1449 	return (prefix);
1450 }
1451