status.c revision 1.99
1/* $OpenBSD: status.c,v 1.99 2013/03/21 16:25:08 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
32char   *status_redraw_get_left(
33	    struct client *, time_t, int, struct grid_cell *, size_t *);
34char   *status_redraw_get_right(
35	    struct client *, time_t, int, struct grid_cell *, size_t *);
36char   *status_find_job(struct client *, char **);
37void	status_job_free(void *);
38void	status_job_callback(struct job *);
39char   *status_print(
40	    struct client *, struct winlink *, time_t, struct grid_cell *);
41void	status_replace1(struct client *, struct session *, struct winlink *,
42	    struct window_pane *, char **, char **, char *, size_t, int);
43void	status_message_callback(int, short, void *);
44
45const char *status_prompt_up_history(u_int *);
46const char *status_prompt_down_history(u_int *);
47void	status_prompt_add_history(const char *);
48char   *status_prompt_complete(const char *);
49
50/* Status prompt history. */
51ARRAY_DECL(, char *) status_prompt_history = ARRAY_INITIALIZER;
52
53/* Status output tree. */
54RB_GENERATE(status_out_tree, status_out, entry, status_out_cmp);
55
56/* Output tree comparison function. */
57int
58status_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. */
64int
65status_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. */
78char *
79status_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. */
108char *
109status_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. */
138void
139status_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. */
155int
156status_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
316draw:
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
373out:
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 #). */
386void
387status_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	errno = 0;
397	limit = strtol(*iptr, &endptr, 10);
398	if ((limit == 0 && errno != EINVAL) ||
399	    (limit == LONG_MIN && errno != ERANGE) ||
400	    (limit == LONG_MAX && errno != ERANGE) ||
401	    limit != 0)
402		*iptr = endptr;
403	if (limit <= 0)
404		limit = LONG_MAX;
405
406	freeptr = NULL;
407
408	switch (*(*iptr)++) {
409	case '(':
410		if (!jobsflag) {
411			ch = ')';
412			goto skip_to;
413		}
414		if ((ptr = status_find_job(c, iptr)) == NULL)
415			return;
416		goto do_replace;
417	case 'D':
418		xsnprintf(tmp, sizeof tmp, "%%%u", wp->id);
419		ptr = tmp;
420		goto do_replace;
421	case 'H':
422		if (gethostname(tmp, sizeof tmp) != 0)
423			fatal("gethostname failed");
424		ptr = tmp;
425		goto do_replace;
426	case 'h':
427		if (gethostname(tmp, sizeof tmp) != 0)
428			fatal("gethostname failed");
429		if ((ptr = strchr(tmp, '.')) != NULL)
430			*ptr = '\0';
431		ptr = tmp;
432		goto do_replace;
433	case 'I':
434		xsnprintf(tmp, sizeof tmp, "%d", wl->idx);
435		ptr = tmp;
436		goto do_replace;
437	case 'P':
438		if (window_pane_index(wp, &idx) != 0)
439			fatalx("index not found");
440		xsnprintf(tmp, sizeof tmp, "%u", idx);
441		ptr = tmp;
442		goto do_replace;
443	case 'S':
444		ptr = s->name;
445		goto do_replace;
446	case 'T':
447		ptr = wp->base.title;
448		goto do_replace;
449	case 'W':
450		ptr = wl->window->name;
451		goto do_replace;
452	case 'F':
453		ptr = window_printable_flags(s, wl);
454		freeptr = ptr;
455		goto do_replace;
456	case '[':
457		/*
458		 * Embedded style, handled at display time. Leave present and
459		 * skip input until ].
460		 */
461		ch = ']';
462		goto skip_to;
463	case '{':
464		ptr = (char *) "#{";
465		goto do_replace;
466	case '#':
467		*(*optr)++ = '#';
468		break;
469	}
470
471	return;
472
473do_replace:
474	ptrlen = strlen(ptr);
475	if ((size_t) limit < ptrlen)
476		ptrlen = limit;
477
478	if (*optr + ptrlen >= out + outsize - 1)
479		goto out;
480	while (ptrlen > 0 && *ptr != '\0') {
481		*(*optr)++ = *ptr++;
482		ptrlen--;
483	}
484
485out:
486	free(freeptr);
487	return;
488
489skip_to:
490	*(*optr)++ = '#';
491
492	(*iptr)--;	/* include ch */
493	while (**iptr != ch && **iptr != '\0') {
494		if (*optr >=  out + outsize - 1)
495			break;
496		*(*optr)++ = *(*iptr)++;
497	}
498}
499
500/* Replace special sequences in fmt. */
501char *
502status_replace(struct client *c, struct session *s, struct winlink *wl,
503    struct window_pane *wp, const char *fmt, time_t t, int jobsflag)
504{
505	static char		 out[BUFSIZ];
506	char			 in[BUFSIZ], ch, *iptr, *optr, *expanded;
507	size_t			 len;
508	struct format_tree	*ft;
509
510	if (fmt == NULL)
511		return (xstrdup(""));
512
513	if (s == NULL)
514		s = c->session;
515	if (wl == NULL)
516		wl = s->curw;
517	if (wp == NULL)
518		wp = wl->window->active;
519
520	len = strftime(in, sizeof in, fmt, localtime(&t));
521	in[len] = '\0';
522
523	iptr = in;
524	optr = out;
525
526	while (*iptr != '\0') {
527		if (optr >= out + (sizeof out) - 1)
528			break;
529		ch = *iptr++;
530
531		if (ch != '#' || *iptr == '\0') {
532			*optr++ = ch;
533			continue;
534		}
535		status_replace1(
536		    c, s, wl, wp, &iptr, &optr, out, sizeof out, jobsflag);
537	}
538	*optr = '\0';
539
540	ft = format_create();
541	format_client(ft, c);
542	format_session(ft, s);
543	format_winlink(ft, s, wl);
544	format_window_pane(ft, wp);
545	expanded = format_expand(ft, out);
546	format_free(ft);
547	return (expanded);
548}
549
550/* Figure out job name and get its result, starting it off if necessary. */
551char *
552status_find_job(struct client *c, char **iptr)
553{
554	struct status_out	*so, so_find;
555	char   			*cmd;
556	int			 lastesc;
557	size_t			 len;
558
559	if (**iptr == '\0')
560		return (NULL);
561	if (**iptr == ')') {		/* no command given */
562		(*iptr)++;
563		return (NULL);
564	}
565
566	cmd = xmalloc(strlen(*iptr) + 1);
567	len = 0;
568
569	lastesc = 0;
570	for (; **iptr != '\0'; (*iptr)++) {
571		if (!lastesc && **iptr == ')')
572			break;		/* unescaped ) is the end */
573		if (!lastesc && **iptr == '\\') {
574			lastesc = 1;
575			continue;	/* skip \ if not escaped */
576		}
577		lastesc = 0;
578		cmd[len++] = **iptr;
579	}
580	if (**iptr == '\0')		/* no terminating ) */ {
581		free(cmd);
582		return (NULL);
583	}
584	(*iptr)++;			/* skip final ) */
585	cmd[len] = '\0';
586
587	/* First try in the new tree. */
588	so_find.cmd = cmd;
589	so = RB_FIND(status_out_tree, &c->status_new, &so_find);
590	if (so != NULL && so->out != NULL) {
591		free(cmd);
592		return (so->out);
593	}
594
595	/* If not found at all, start the job and add to the tree. */
596	if (so == NULL) {
597		job_run(cmd, status_job_callback, status_job_free, c);
598		c->references++;
599
600		so = xmalloc(sizeof *so);
601		so->cmd = xstrdup(cmd);
602		so->out = NULL;
603		RB_INSERT(status_out_tree, &c->status_new, so);
604	}
605
606	/* Lookup in the old tree. */
607	so_find.cmd = cmd;
608	so = RB_FIND(status_out_tree, &c->status_old, &so_find);
609	free(cmd);
610	if (so != NULL)
611		return (so->out);
612	return (NULL);
613}
614
615/* Free job tree. */
616void
617status_free_jobs(struct status_out_tree *sotree)
618{
619	struct status_out	*so, *so_next;
620
621	so_next = RB_MIN(status_out_tree, sotree);
622	while (so_next != NULL) {
623		so = so_next;
624		so_next = RB_NEXT(status_out_tree, sotree, so);
625
626		RB_REMOVE(status_out_tree, sotree, so);
627		free(so->out);
628		free(so->cmd);
629		free(so);
630	}
631}
632
633/* Update jobs on status interval. */
634void
635status_update_jobs(struct client *c)
636{
637	/* Free the old tree. */
638	status_free_jobs(&c->status_old);
639
640	/* Move the new to old. */
641	memcpy(&c->status_old, &c->status_new, sizeof c->status_old);
642	RB_INIT(&c->status_new);
643}
644
645/* Free status job. */
646void
647status_job_free(void *data)
648{
649	struct client	*c = data;
650
651	c->references--;
652}
653
654/* Job has finished: save its result. */
655void
656status_job_callback(struct job *job)
657{
658	struct client		*c = job->data;
659	struct status_out	*so, so_find;
660	char			*line, *buf;
661	size_t			 len;
662
663	if (c->flags & CLIENT_DEAD)
664		return;
665
666	so_find.cmd = job->cmd;
667	so = RB_FIND(status_out_tree, &c->status_new, &so_find);
668	if (so == NULL || so->out != NULL)
669		return;
670
671	buf = NULL;
672	if ((line = evbuffer_readline(job->event->input)) == NULL) {
673		len = EVBUFFER_LENGTH(job->event->input);
674		buf = xmalloc(len + 1);
675		if (len != 0)
676			memcpy(buf, EVBUFFER_DATA(job->event->input), len);
677		buf[len] = '\0';
678	} else
679		buf = xstrdup(line);
680
681	so->out = buf;
682	server_status_client(c);
683}
684
685/* Return winlink status line entry and adjust gc as necessary. */
686char *
687status_print(
688    struct client *c, struct winlink *wl, time_t t, struct grid_cell *gc)
689{
690	struct options	*oo = &wl->window->options;
691	struct session	*s = c->session;
692	const char	*fmt;
693	char   		*text;
694	int		 fg, bg, attr;
695
696	fg = options_get_number(oo, "window-status-fg");
697	if (fg != 8)
698		colour_set_fg(gc, fg);
699	bg = options_get_number(oo, "window-status-bg");
700	if (bg != 8)
701		colour_set_bg(gc, bg);
702	attr = options_get_number(oo, "window-status-attr");
703	if (attr != 0)
704		gc->attr = attr;
705	fmt = options_get_string(oo, "window-status-format");
706	if (wl == s->curw) {
707		fg = options_get_number(oo, "window-status-current-fg");
708		if (fg != 8)
709			colour_set_fg(gc, fg);
710		bg = options_get_number(oo, "window-status-current-bg");
711		if (bg != 8)
712			colour_set_bg(gc, bg);
713		attr = options_get_number(oo, "window-status-current-attr");
714		if (attr != 0)
715			gc->attr = attr;
716		fmt = options_get_string(oo, "window-status-current-format");
717	}
718	if (wl == TAILQ_FIRST(&s->lastw)) {
719		fg = options_get_number(oo, "window-status-last-fg");
720		if (fg != 8)
721			colour_set_fg(gc, fg);
722		bg = options_get_number(oo, "window-status-last-bg");
723		if (bg != 8)
724			colour_set_bg(gc, bg);
725		attr = options_get_number(oo, "window-status-last-attr");
726		if (attr != 0)
727			gc->attr = attr;
728	}
729
730	if (wl->flags & WINLINK_BELL) {
731		fg = options_get_number(oo, "window-status-bell-fg");
732		if (fg != 8)
733			colour_set_fg(gc, fg);
734		bg = options_get_number(oo, "window-status-bell-bg");
735		if (bg != 8)
736			colour_set_bg(gc, bg);
737		attr = options_get_number(oo, "window-status-bell-attr");
738		if (attr != 0)
739			gc->attr = attr;
740	} else if (wl->flags & WINLINK_CONTENT) {
741		fg = options_get_number(oo, "window-status-content-fg");
742		if (fg != 8)
743			colour_set_fg(gc, fg);
744		bg = options_get_number(oo, "window-status-content-bg");
745		if (bg != 8)
746			colour_set_bg(gc, bg);
747		attr = options_get_number(oo, "window-status-content-attr");
748		if (attr != 0)
749			gc->attr = attr;
750	} else if (wl->flags & (WINLINK_ACTIVITY|WINLINK_SILENCE)) {
751		fg = options_get_number(oo, "window-status-activity-fg");
752		if (fg != 8)
753			colour_set_fg(gc, fg);
754		bg = options_get_number(oo, "window-status-activity-bg");
755		if (bg != 8)
756			colour_set_bg(gc, bg);
757		attr = options_get_number(oo, "window-status-activity-attr");
758		if (attr != 0)
759			gc->attr = attr;
760	}
761
762	text = status_replace(c, NULL, wl, NULL, fmt, t, 1);
763	return (text);
764}
765
766/* Set a status line message. */
767void printflike2
768status_message_set(struct client *c, const char *fmt, ...)
769{
770	struct timeval		 tv;
771	struct session		*s = c->session;
772	struct message_entry	*msg;
773	va_list			 ap;
774	int			 delay;
775	u_int			 i, limit;
776
777	status_prompt_clear(c);
778	status_message_clear(c);
779
780	va_start(ap, fmt);
781	xvasprintf(&c->message_string, fmt, ap);
782	va_end(ap);
783
784	ARRAY_EXPAND(&c->message_log, 1);
785	msg = &ARRAY_LAST(&c->message_log);
786	msg->msg_time = time(NULL);
787	msg->msg = xstrdup(c->message_string);
788
789	if (s == NULL)
790		limit = 0;
791	else
792		limit = options_get_number(&s->options, "message-limit");
793	if (ARRAY_LENGTH(&c->message_log) > limit) {
794		limit = ARRAY_LENGTH(&c->message_log) - limit;
795		for (i = 0; i < limit; i++) {
796			msg = &ARRAY_FIRST(&c->message_log);
797			free(msg->msg);
798			ARRAY_REMOVE(&c->message_log, 0);
799		}
800	}
801
802	delay = options_get_number(&c->session->options, "display-time");
803	tv.tv_sec = delay / 1000;
804	tv.tv_usec = (delay % 1000) * 1000L;
805
806	if (event_initialized (&c->message_timer))
807		evtimer_del(&c->message_timer);
808	evtimer_set(&c->message_timer, status_message_callback, c);
809	evtimer_add(&c->message_timer, &tv);
810
811	c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
812	c->flags |= CLIENT_STATUS;
813}
814
815/* Clear status line message. */
816void
817status_message_clear(struct client *c)
818{
819	if (c->message_string == NULL)
820		return;
821
822	free(c->message_string);
823	c->message_string = NULL;
824
825	c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
826	c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
827
828	screen_reinit(&c->status);
829}
830
831/* Clear status line message after timer expires. */
832/* ARGSUSED */
833void
834status_message_callback(unused int fd, unused short event, void *data)
835{
836	struct client	*c = data;
837
838	status_message_clear(c);
839}
840
841/* Draw client message on status line of present else on last line. */
842int
843status_message_redraw(struct client *c)
844{
845	struct screen_write_ctx		ctx;
846	struct session		       *s = c->session;
847	struct screen		        old_status;
848	size_t			        len;
849	struct grid_cell		gc;
850	int				utf8flag;
851
852	if (c->tty.sx == 0 || c->tty.sy == 0)
853		return (0);
854	memcpy(&old_status, &c->status, sizeof old_status);
855	screen_init(&c->status, c->tty.sx, 1, 0);
856
857	utf8flag = options_get_number(&s->options, "status-utf8");
858
859	len = screen_write_strlen(utf8flag, "%s", c->message_string);
860	if (len > c->tty.sx)
861		len = c->tty.sx;
862
863	memcpy(&gc, &grid_default_cell, sizeof gc);
864	colour_set_fg(&gc, options_get_number(&s->options, "message-fg"));
865	colour_set_bg(&gc, options_get_number(&s->options, "message-bg"));
866	gc.attr |= options_get_number(&s->options, "message-attr");
867
868	screen_write_start(&ctx, NULL, &c->status);
869
870	screen_write_cursormove(&ctx, 0, 0);
871	screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->message_string);
872	for (; len < c->tty.sx; len++)
873		screen_write_putc(&ctx, &gc, ' ');
874
875	screen_write_stop(&ctx);
876
877	if (grid_compare(c->status.grid, old_status.grid) == 0) {
878		screen_free(&old_status);
879		return (0);
880	}
881	screen_free(&old_status);
882	return (1);
883}
884
885/* Enable status line prompt. */
886void
887status_prompt_set(struct client *c, const char *msg, const char *input,
888    int (*callbackfn)(void *, const char *), void (*freefn)(void *),
889    void *data, int flags)
890{
891	int	keys;
892
893	status_message_clear(c);
894	status_prompt_clear(c);
895
896	c->prompt_string = status_replace(c, NULL, NULL, NULL, msg,
897	    time(NULL), 0);
898
899	c->prompt_buffer = status_replace(c, NULL, NULL, NULL, input,
900	    time(NULL), 0);
901	c->prompt_index = strlen(c->prompt_buffer);
902
903	c->prompt_callbackfn = callbackfn;
904	c->prompt_freefn = freefn;
905	c->prompt_data = data;
906
907	c->prompt_hindex = 0;
908
909	c->prompt_flags = flags;
910
911	keys = options_get_number(&c->session->options, "status-keys");
912	if (keys == MODEKEY_EMACS)
913		mode_key_init(&c->prompt_mdata, &mode_key_tree_emacs_edit);
914	else
915		mode_key_init(&c->prompt_mdata, &mode_key_tree_vi_edit);
916
917	c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
918	c->flags |= CLIENT_STATUS;
919}
920
921/* Remove status line prompt. */
922void
923status_prompt_clear(struct client *c)
924{
925	if (c->prompt_string == NULL)
926		return;
927
928	if (c->prompt_freefn != NULL && c->prompt_data != NULL)
929		c->prompt_freefn(c->prompt_data);
930
931	free(c->prompt_string);
932	c->prompt_string = NULL;
933
934	free(c->prompt_buffer);
935	c->prompt_buffer = NULL;
936
937	c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
938	c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
939
940	screen_reinit(&c->status);
941}
942
943/* Update status line prompt with a new prompt string. */
944void
945status_prompt_update(struct client *c, const char *msg, const char *input)
946{
947	free(c->prompt_string);
948	c->prompt_string = status_replace(c, NULL, NULL, NULL, msg,
949	    time(NULL), 0);
950
951	free(c->prompt_buffer);
952	c->prompt_buffer = status_replace(c, NULL, NULL, NULL, input,
953	    time(NULL), 0);
954	c->prompt_index = strlen(c->prompt_buffer);
955
956	c->prompt_hindex = 0;
957
958	c->flags |= CLIENT_STATUS;
959}
960
961/* Draw client prompt on status line of present else on last line. */
962int
963status_prompt_redraw(struct client *c)
964{
965	struct screen_write_ctx		ctx;
966	struct session		       *s = c->session;
967	struct screen		        old_status;
968	size_t			        i, size, left, len, off;
969	struct grid_cell		gc, *gcp;
970	int				utf8flag;
971
972	if (c->tty.sx == 0 || c->tty.sy == 0)
973		return (0);
974	memcpy(&old_status, &c->status, sizeof old_status);
975	screen_init(&c->status, c->tty.sx, 1, 0);
976
977	utf8flag = options_get_number(&s->options, "status-utf8");
978
979	len = screen_write_strlen(utf8flag, "%s", c->prompt_string);
980	if (len > c->tty.sx)
981		len = c->tty.sx;
982	off = 0;
983
984	memcpy(&gc, &grid_default_cell, sizeof gc);
985	/* Change colours for command mode. */
986	if (c->prompt_mdata.mode == 1) {
987		colour_set_fg(&gc, options_get_number(&s->options, "message-command-fg"));
988		colour_set_bg(&gc, options_get_number(&s->options, "message-command-bg"));
989		gc.attr |= options_get_number(&s->options, "message-command-attr");
990	} else {
991		colour_set_fg(&gc, options_get_number(&s->options, "message-fg"));
992		colour_set_bg(&gc, options_get_number(&s->options, "message-bg"));
993		gc.attr |= options_get_number(&s->options, "message-attr");
994	}
995
996	screen_write_start(&ctx, NULL, &c->status);
997
998	screen_write_cursormove(&ctx, 0, 0);
999	screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->prompt_string);
1000
1001	left = c->tty.sx - len;
1002	if (left != 0) {
1003		size = screen_write_strlen(utf8flag, "%s", c->prompt_buffer);
1004		if (c->prompt_index >= left) {
1005			off = c->prompt_index - left + 1;
1006			if (c->prompt_index == size)
1007				left--;
1008			size = left;
1009		}
1010		screen_write_nputs(
1011		    &ctx, left, &gc, utf8flag, "%s", c->prompt_buffer + off);
1012
1013		for (i = len + size; i < c->tty.sx; i++)
1014			screen_write_putc(&ctx, &gc, ' ');
1015	}
1016
1017	screen_write_stop(&ctx);
1018
1019	/* Apply fake cursor. */
1020	off = len + c->prompt_index - off;
1021	gcp = grid_view_get_cell(c->status.grid, off, 0);
1022	gcp->attr ^= GRID_ATTR_REVERSE;
1023
1024	if (grid_compare(c->status.grid, old_status.grid) == 0) {
1025		screen_free(&old_status);
1026		return (0);
1027	}
1028	screen_free(&old_status);
1029	return (1);
1030}
1031
1032/* Handle keys in prompt. */
1033void
1034status_prompt_key(struct client *c, int key)
1035{
1036	struct session		*sess = c->session;
1037	struct options		*oo = &sess->options;
1038	struct paste_buffer	*pb;
1039	char			*s, *first, *last, word[64], swapc;
1040	const char		*histstr;
1041	const char		*wsep = NULL;
1042	u_char			 ch;
1043	size_t			 size, n, off, idx;
1044
1045	size = strlen(c->prompt_buffer);
1046	switch (mode_key_lookup(&c->prompt_mdata, key)) {
1047	case MODEKEYEDIT_CURSORLEFT:
1048		if (c->prompt_index > 0) {
1049			c->prompt_index--;
1050			c->flags |= CLIENT_STATUS;
1051		}
1052		break;
1053	case MODEKEYEDIT_SWITCHMODE:
1054		c->flags |= CLIENT_STATUS;
1055		break;
1056	case MODEKEYEDIT_SWITCHMODEAPPEND:
1057		c->flags |= CLIENT_STATUS;
1058		/* FALLTHROUGH */
1059	case MODEKEYEDIT_CURSORRIGHT:
1060		if (c->prompt_index < size) {
1061			c->prompt_index++;
1062			c->flags |= CLIENT_STATUS;
1063		}
1064		break;
1065	case MODEKEYEDIT_SWITCHMODEBEGINLINE:
1066		c->flags |= CLIENT_STATUS;
1067		/* FALLTHROUGH */
1068	case MODEKEYEDIT_STARTOFLINE:
1069		if (c->prompt_index != 0) {
1070			c->prompt_index = 0;
1071			c->flags |= CLIENT_STATUS;
1072		}
1073		break;
1074	case MODEKEYEDIT_SWITCHMODEAPPENDLINE:
1075		c->flags |= CLIENT_STATUS;
1076		/* FALLTHROUGH */
1077	case MODEKEYEDIT_ENDOFLINE:
1078		if (c->prompt_index != size) {
1079			c->prompt_index = size;
1080			c->flags |= CLIENT_STATUS;
1081		}
1082		break;
1083	case MODEKEYEDIT_COMPLETE:
1084		if (*c->prompt_buffer == '\0')
1085			break;
1086
1087		idx = c->prompt_index;
1088		if (idx != 0)
1089			idx--;
1090
1091		/* Find the word we are in. */
1092		first = c->prompt_buffer + idx;
1093		while (first > c->prompt_buffer && *first != ' ')
1094			first--;
1095		while (*first == ' ')
1096			first++;
1097		last = c->prompt_buffer + idx;
1098		while (*last != '\0' && *last != ' ')
1099			last++;
1100		while (*last == ' ')
1101			last--;
1102		if (*last != '\0')
1103			last++;
1104		if (last <= first ||
1105		    ((size_t) (last - first)) > (sizeof word) - 1)
1106			break;
1107		memcpy(word, first, last - first);
1108		word[last - first] = '\0';
1109
1110		/* And try to complete it. */
1111		if ((s = status_prompt_complete(word)) == NULL)
1112			break;
1113
1114		/* Trim out word. */
1115		n = size - (last - c->prompt_buffer) + 1; /* with \0 */
1116		memmove(first, last, n);
1117		size -= last - first;
1118
1119		/* Insert the new word. */
1120		size += strlen(s);
1121		off = first - c->prompt_buffer;
1122		c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 1);
1123		first = c->prompt_buffer + off;
1124		memmove(first + strlen(s), first, n);
1125		memcpy(first, s, strlen(s));
1126
1127		c->prompt_index = (first - c->prompt_buffer) + strlen(s);
1128		free(s);
1129
1130		c->flags |= CLIENT_STATUS;
1131		break;
1132	case MODEKEYEDIT_BACKSPACE:
1133		if (c->prompt_index != 0) {
1134			if (c->prompt_index == size)
1135				c->prompt_buffer[--c->prompt_index] = '\0';
1136			else {
1137				memmove(c->prompt_buffer + c->prompt_index - 1,
1138				    c->prompt_buffer + c->prompt_index,
1139				    size + 1 - c->prompt_index);
1140				c->prompt_index--;
1141			}
1142			c->flags |= CLIENT_STATUS;
1143		}
1144		break;
1145	case MODEKEYEDIT_DELETE:
1146		if (c->prompt_index != size) {
1147			memmove(c->prompt_buffer + c->prompt_index,
1148			    c->prompt_buffer + c->prompt_index + 1,
1149			    size + 1 - c->prompt_index);
1150			c->flags |= CLIENT_STATUS;
1151		}
1152		break;
1153	case MODEKEYEDIT_DELETELINE:
1154		*c->prompt_buffer = '\0';
1155		c->prompt_index = 0;
1156		c->flags |= CLIENT_STATUS;
1157		break;
1158	case MODEKEYEDIT_DELETETOENDOFLINE:
1159		if (c->prompt_index < size) {
1160			c->prompt_buffer[c->prompt_index] = '\0';
1161			c->flags |= CLIENT_STATUS;
1162		}
1163		break;
1164	case MODEKEYEDIT_DELETEWORD:
1165		wsep = options_get_string(oo, "word-separators");
1166		idx = c->prompt_index;
1167
1168		/* Find a non-separator. */
1169		while (idx != 0) {
1170			idx--;
1171			if (!strchr(wsep, c->prompt_buffer[idx]))
1172				break;
1173		}
1174
1175		/* Find the separator at the beginning of the word. */
1176		while (idx != 0) {
1177			idx--;
1178			if (strchr(wsep, c->prompt_buffer[idx])) {
1179				/* Go back to the word. */
1180				idx++;
1181				break;
1182			}
1183		}
1184
1185		memmove(c->prompt_buffer + idx,
1186		    c->prompt_buffer + c->prompt_index,
1187		    size + 1 - c->prompt_index);
1188		memset(c->prompt_buffer + size - (c->prompt_index - idx),
1189		    '\0', c->prompt_index - idx);
1190		c->prompt_index = idx;
1191		c->flags |= CLIENT_STATUS;
1192		break;
1193	case MODEKEYEDIT_NEXTSPACE:
1194		wsep = " ";
1195		/* FALLTHROUGH */
1196	case MODEKEYEDIT_NEXTWORD:
1197		if (wsep == NULL)
1198			wsep = options_get_string(oo, "word-separators");
1199
1200		/* Find a separator. */
1201		while (c->prompt_index != size) {
1202			c->prompt_index++;
1203			if (strchr(wsep, c->prompt_buffer[c->prompt_index]))
1204				break;
1205		}
1206
1207		/* Find the word right after the separation. */
1208		while (c->prompt_index != size) {
1209			c->prompt_index++;
1210			if (!strchr(wsep, c->prompt_buffer[c->prompt_index]))
1211				break;
1212		}
1213
1214		c->flags |= CLIENT_STATUS;
1215		break;
1216	case MODEKEYEDIT_NEXTSPACEEND:
1217		wsep = " ";
1218		/* FALLTHROUGH */
1219	case MODEKEYEDIT_NEXTWORDEND:
1220		if (wsep == NULL)
1221			wsep = options_get_string(oo, "word-separators");
1222
1223		/* Find a word. */
1224		while (c->prompt_index != size) {
1225			c->prompt_index++;
1226			if (!strchr(wsep, c->prompt_buffer[c->prompt_index]))
1227				break;
1228		}
1229
1230		/* Find the separator at the end of the word. */
1231		while (c->prompt_index != size) {
1232			c->prompt_index++;
1233			if (strchr(wsep, c->prompt_buffer[c->prompt_index]))
1234				break;
1235		}
1236
1237		c->flags |= CLIENT_STATUS;
1238		break;
1239	case MODEKEYEDIT_PREVIOUSSPACE:
1240		wsep = " ";
1241		/* FALLTHROUGH */
1242	case MODEKEYEDIT_PREVIOUSWORD:
1243		if (wsep == NULL)
1244			wsep = options_get_string(oo, "word-separators");
1245
1246		/* Find a non-separator. */
1247		while (c->prompt_index != 0) {
1248			c->prompt_index--;
1249			if (!strchr(wsep, c->prompt_buffer[c->prompt_index]))
1250				break;
1251		}
1252
1253		/* Find the separator at the beginning of the word. */
1254		while (c->prompt_index != 0) {
1255			c->prompt_index--;
1256			if (strchr(wsep, c->prompt_buffer[c->prompt_index])) {
1257				/* Go back to the word. */
1258				c->prompt_index++;
1259				break;
1260			}
1261		}
1262
1263		c->flags |= CLIENT_STATUS;
1264		break;
1265	case MODEKEYEDIT_HISTORYUP:
1266		histstr = status_prompt_up_history(&c->prompt_hindex);
1267		if (histstr == NULL)
1268			break;
1269		free(c->prompt_buffer);
1270		c->prompt_buffer = xstrdup(histstr);
1271		c->prompt_index = strlen(c->prompt_buffer);
1272		c->flags |= CLIENT_STATUS;
1273		break;
1274	case MODEKEYEDIT_HISTORYDOWN:
1275		histstr = status_prompt_down_history(&c->prompt_hindex);
1276		if (histstr == NULL)
1277			break;
1278		free(c->prompt_buffer);
1279		c->prompt_buffer = xstrdup(histstr);
1280		c->prompt_index = strlen(c->prompt_buffer);
1281		c->flags |= CLIENT_STATUS;
1282		break;
1283	case MODEKEYEDIT_PASTE:
1284		if ((pb = paste_get_top(&global_buffers)) == NULL)
1285			break;
1286		for (n = 0; n < pb->size; n++) {
1287			ch = (u_char) pb->data[n];
1288			if (ch < 32 || ch == 127)
1289				break;
1290		}
1291
1292		c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + n + 1);
1293		if (c->prompt_index == size) {
1294			memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
1295			c->prompt_index += n;
1296			c->prompt_buffer[c->prompt_index] = '\0';
1297		} else {
1298			memmove(c->prompt_buffer + c->prompt_index + n,
1299			    c->prompt_buffer + c->prompt_index,
1300			    size + 1 - c->prompt_index);
1301			memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
1302			c->prompt_index += n;
1303		}
1304
1305		c->flags |= CLIENT_STATUS;
1306		break;
1307	case MODEKEYEDIT_TRANSPOSECHARS:
1308		idx = c->prompt_index;
1309		if (idx < size)
1310			idx++;
1311		if (idx >= 2) {
1312			swapc = c->prompt_buffer[idx - 2];
1313			c->prompt_buffer[idx - 2] = c->prompt_buffer[idx - 1];
1314			c->prompt_buffer[idx - 1] = swapc;
1315			c->prompt_index = idx;
1316			c->flags |= CLIENT_STATUS;
1317		}
1318		break;
1319	case MODEKEYEDIT_ENTER:
1320		if (*c->prompt_buffer != '\0')
1321			status_prompt_add_history(c->prompt_buffer);
1322		if (c->prompt_callbackfn(c->prompt_data, c->prompt_buffer) == 0)
1323			status_prompt_clear(c);
1324		break;
1325	case MODEKEYEDIT_CANCEL:
1326		if (c->prompt_callbackfn(c->prompt_data, NULL) == 0)
1327			status_prompt_clear(c);
1328		break;
1329	case MODEKEY_OTHER:
1330		if ((key & 0xff00) != 0 || key < 32 || key == 127)
1331			break;
1332		c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 2);
1333
1334		if (c->prompt_index == size) {
1335			c->prompt_buffer[c->prompt_index++] = key;
1336			c->prompt_buffer[c->prompt_index] = '\0';
1337		} else {
1338			memmove(c->prompt_buffer + c->prompt_index + 1,
1339			    c->prompt_buffer + c->prompt_index,
1340			    size + 1 - c->prompt_index);
1341			c->prompt_buffer[c->prompt_index++] = key;
1342		}
1343
1344		if (c->prompt_flags & PROMPT_SINGLE) {
1345			if (c->prompt_callbackfn(
1346			    c->prompt_data, c->prompt_buffer) == 0)
1347				status_prompt_clear(c);
1348		}
1349
1350		c->flags |= CLIENT_STATUS;
1351		break;
1352	default:
1353		break;
1354	}
1355}
1356
1357/* Get previous line from the history. */
1358const char *
1359status_prompt_up_history(u_int *idx)
1360{
1361	u_int size;
1362
1363	/*
1364	 * History runs from 0 to size - 1.
1365	 *
1366	 * Index is from 0 to size. Zero is empty.
1367	 */
1368
1369	size = ARRAY_LENGTH(&status_prompt_history);
1370	if (size == 0 || *idx == size)
1371		return (NULL);
1372	(*idx)++;
1373	return (ARRAY_ITEM(&status_prompt_history, size - *idx));
1374}
1375
1376/* Get next line from the history. */
1377const char *
1378status_prompt_down_history(u_int *idx)
1379{
1380	u_int size;
1381
1382	size = ARRAY_LENGTH(&status_prompt_history);
1383	if (size == 0 || *idx == 0)
1384		return ("");
1385	(*idx)--;
1386	if (*idx == 0)
1387		return ("");
1388	return (ARRAY_ITEM(&status_prompt_history, size - *idx));
1389}
1390
1391/* Add line to the history. */
1392void
1393status_prompt_add_history(const char *line)
1394{
1395	u_int size;
1396
1397	size = ARRAY_LENGTH(&status_prompt_history);
1398	if (size > 0 && strcmp(ARRAY_LAST(&status_prompt_history), line) == 0)
1399		return;
1400
1401	if (size == PROMPT_HISTORY) {
1402		free(ARRAY_FIRST(&status_prompt_history));
1403		ARRAY_REMOVE(&status_prompt_history, 0);
1404	}
1405
1406	ARRAY_ADD(&status_prompt_history, xstrdup(line));
1407}
1408
1409/* Complete word. */
1410char *
1411status_prompt_complete(const char *s)
1412{
1413	const struct cmd_entry 	  	       **cmdent;
1414	const struct options_table_entry	*oe;
1415	ARRAY_DECL(, const char *)		 list;
1416	char					*prefix, *s2;
1417	u_int					 i;
1418	size_t				 	 j;
1419
1420	if (*s == '\0')
1421		return (NULL);
1422
1423	/* First, build a list of all the possible matches. */
1424	ARRAY_INIT(&list);
1425	for (cmdent = cmd_table; *cmdent != NULL; cmdent++) {
1426		if (strncmp((*cmdent)->name, s, strlen(s)) == 0)
1427			ARRAY_ADD(&list, (*cmdent)->name);
1428	}
1429	for (oe = server_options_table; oe->name != NULL; oe++) {
1430		if (strncmp(oe->name, s, strlen(s)) == 0)
1431			ARRAY_ADD(&list, oe->name);
1432	}
1433	for (oe = session_options_table; oe->name != NULL; oe++) {
1434		if (strncmp(oe->name, s, strlen(s)) == 0)
1435			ARRAY_ADD(&list, oe->name);
1436	}
1437	for (oe = window_options_table; oe->name != NULL; oe++) {
1438		if (strncmp(oe->name, s, strlen(s)) == 0)
1439			ARRAY_ADD(&list, oe->name);
1440	}
1441
1442	/* If none, bail now. */
1443	if (ARRAY_LENGTH(&list) == 0) {
1444		ARRAY_FREE(&list);
1445		return (NULL);
1446	}
1447
1448	/* If an exact match, return it, with a trailing space. */
1449	if (ARRAY_LENGTH(&list) == 1) {
1450		xasprintf(&s2, "%s ", ARRAY_FIRST(&list));
1451		ARRAY_FREE(&list);
1452		return (s2);
1453	}
1454
1455	/* Now loop through the list and find the longest common prefix. */
1456	prefix = xstrdup(ARRAY_FIRST(&list));
1457	for (i = 1; i < ARRAY_LENGTH(&list); i++) {
1458		s = ARRAY_ITEM(&list, i);
1459
1460		j = strlen(s);
1461		if (j > strlen(prefix))
1462			j = strlen(prefix);
1463		for (; j > 0; j--) {
1464			if (prefix[j - 1] != s[j - 1])
1465				prefix[j - 1] = '\0';
1466		}
1467	}
1468
1469	ARRAY_FREE(&list);
1470	return (prefix);
1471}
1472