status.c revision 1.103
1/* $OpenBSD: status.c,v 1.103 2013/03/25 11:43: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
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, NULL, 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 = 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. */
832void
833status_message_callback(unused int fd, unused short event, void *data)
834{
835	struct client	*c = data;
836
837	status_message_clear(c);
838}
839
840/* Draw client message on status line of present else on last line. */
841int
842status_message_redraw(struct client *c)
843{
844	struct screen_write_ctx		ctx;
845	struct session		       *s = c->session;
846	struct screen		        old_status;
847	size_t			        len;
848	struct grid_cell		gc;
849	int				utf8flag;
850
851	if (c->tty.sx == 0 || c->tty.sy == 0)
852		return (0);
853	memcpy(&old_status, &c->status, sizeof old_status);
854	screen_init(&c->status, c->tty.sx, 1, 0);
855
856	utf8flag = options_get_number(&s->options, "status-utf8");
857
858	len = screen_write_strlen(utf8flag, "%s", c->message_string);
859	if (len > c->tty.sx)
860		len = c->tty.sx;
861
862	memcpy(&gc, &grid_default_cell, sizeof gc);
863	colour_set_fg(&gc, options_get_number(&s->options, "message-fg"));
864	colour_set_bg(&gc, options_get_number(&s->options, "message-bg"));
865	gc.attr |= options_get_number(&s->options, "message-attr");
866
867	screen_write_start(&ctx, NULL, &c->status);
868
869	screen_write_cursormove(&ctx, 0, 0);
870	screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->message_string);
871	for (; len < c->tty.sx; len++)
872		screen_write_putc(&ctx, &gc, ' ');
873
874	screen_write_stop(&ctx);
875
876	if (grid_compare(c->status.grid, old_status.grid) == 0) {
877		screen_free(&old_status);
878		return (0);
879	}
880	screen_free(&old_status);
881	return (1);
882}
883
884/* Enable status line prompt. */
885void
886status_prompt_set(struct client *c, const char *msg, const char *input,
887    int (*callbackfn)(void *, const char *), void (*freefn)(void *),
888    void *data, int flags)
889{
890	int	keys;
891
892	status_message_clear(c);
893	status_prompt_clear(c);
894
895	c->prompt_string = status_replace(c, NULL, NULL, NULL, msg,
896	    time(NULL), 0);
897
898	c->prompt_buffer = status_replace(c, NULL, NULL, NULL, input,
899	    time(NULL), 0);
900	c->prompt_index = strlen(c->prompt_buffer);
901
902	c->prompt_callbackfn = callbackfn;
903	c->prompt_freefn = freefn;
904	c->prompt_data = data;
905
906	c->prompt_hindex = 0;
907
908	c->prompt_flags = flags;
909
910	keys = options_get_number(&c->session->options, "status-keys");
911	if (keys == MODEKEY_EMACS)
912		mode_key_init(&c->prompt_mdata, &mode_key_tree_emacs_edit);
913	else
914		mode_key_init(&c->prompt_mdata, &mode_key_tree_vi_edit);
915
916	c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
917	c->flags |= CLIENT_STATUS;
918}
919
920/* Remove status line prompt. */
921void
922status_prompt_clear(struct client *c)
923{
924	if (c->prompt_string == NULL)
925		return;
926
927	if (c->prompt_freefn != NULL && c->prompt_data != NULL)
928		c->prompt_freefn(c->prompt_data);
929
930	free(c->prompt_string);
931	c->prompt_string = NULL;
932
933	free(c->prompt_buffer);
934	c->prompt_buffer = NULL;
935
936	c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
937	c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
938
939	screen_reinit(&c->status);
940}
941
942/* Update status line prompt with a new prompt string. */
943void
944status_prompt_update(struct client *c, const char *msg, const char *input)
945{
946	free(c->prompt_string);
947	c->prompt_string = status_replace(c, NULL, NULL, NULL, msg,
948	    time(NULL), 0);
949
950	free(c->prompt_buffer);
951	c->prompt_buffer = status_replace(c, NULL, NULL, NULL, input,
952	    time(NULL), 0);
953	c->prompt_index = strlen(c->prompt_buffer);
954
955	c->prompt_hindex = 0;
956
957	c->flags |= CLIENT_STATUS;
958}
959
960/* Draw client prompt on status line of present else on last line. */
961int
962status_prompt_redraw(struct client *c)
963{
964	struct screen_write_ctx		ctx;
965	struct session		       *s = c->session;
966	struct screen		        old_status;
967	size_t			        i, size, left, len, off;
968	struct grid_cell		gc, *gcp;
969	int				utf8flag;
970
971	if (c->tty.sx == 0 || c->tty.sy == 0)
972		return (0);
973	memcpy(&old_status, &c->status, sizeof old_status);
974	screen_init(&c->status, c->tty.sx, 1, 0);
975
976	utf8flag = options_get_number(&s->options, "status-utf8");
977
978	len = screen_write_strlen(utf8flag, "%s", c->prompt_string);
979	if (len > c->tty.sx)
980		len = c->tty.sx;
981	off = 0;
982
983	memcpy(&gc, &grid_default_cell, sizeof gc);
984	/* Change colours for command mode. */
985	if (c->prompt_mdata.mode == 1) {
986		colour_set_fg(&gc, options_get_number(&s->options, "message-command-fg"));
987		colour_set_bg(&gc, options_get_number(&s->options, "message-command-bg"));
988		gc.attr |= options_get_number(&s->options, "message-command-attr");
989	} else {
990		colour_set_fg(&gc, options_get_number(&s->options, "message-fg"));
991		colour_set_bg(&gc, options_get_number(&s->options, "message-bg"));
992		gc.attr |= options_get_number(&s->options, "message-attr");
993	}
994
995	screen_write_start(&ctx, NULL, &c->status);
996
997	screen_write_cursormove(&ctx, 0, 0);
998	screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->prompt_string);
999
1000	left = c->tty.sx - len;
1001	if (left != 0) {
1002		size = screen_write_strlen(utf8flag, "%s", c->prompt_buffer);
1003		if (c->prompt_index >= left) {
1004			off = c->prompt_index - left + 1;
1005			if (c->prompt_index == size)
1006				left--;
1007			size = left;
1008		}
1009		screen_write_nputs(
1010		    &ctx, left, &gc, utf8flag, "%s", c->prompt_buffer + off);
1011
1012		for (i = len + size; i < c->tty.sx; i++)
1013			screen_write_putc(&ctx, &gc, ' ');
1014	}
1015
1016	screen_write_stop(&ctx);
1017
1018	/* Apply fake cursor. */
1019	off = len + c->prompt_index - off;
1020	gcp = grid_view_get_cell(c->status.grid, off, 0);
1021	gcp->attr ^= GRID_ATTR_REVERSE;
1022
1023	if (grid_compare(c->status.grid, old_status.grid) == 0) {
1024		screen_free(&old_status);
1025		return (0);
1026	}
1027	screen_free(&old_status);
1028	return (1);
1029}
1030
1031/* Handle keys in prompt. */
1032void
1033status_prompt_key(struct client *c, int key)
1034{
1035	struct session		*sess = c->session;
1036	struct options		*oo = &sess->options;
1037	struct paste_buffer	*pb;
1038	char			*s, *first, *last, word[64], swapc;
1039	const char		*histstr;
1040	const char		*wsep = NULL;
1041	u_char			 ch;
1042	size_t			 size, n, off, idx;
1043
1044	size = strlen(c->prompt_buffer);
1045	switch (mode_key_lookup(&c->prompt_mdata, key, NULL)) {
1046	case MODEKEYEDIT_CURSORLEFT:
1047		if (c->prompt_index > 0) {
1048			c->prompt_index--;
1049			c->flags |= CLIENT_STATUS;
1050		}
1051		break;
1052	case MODEKEYEDIT_SWITCHMODE:
1053		c->flags |= CLIENT_STATUS;
1054		break;
1055	case MODEKEYEDIT_SWITCHMODEAPPEND:
1056		c->flags |= CLIENT_STATUS;
1057		/* FALLTHROUGH */
1058	case MODEKEYEDIT_CURSORRIGHT:
1059		if (c->prompt_index < size) {
1060			c->prompt_index++;
1061			c->flags |= CLIENT_STATUS;
1062		}
1063		break;
1064	case MODEKEYEDIT_SWITCHMODEBEGINLINE:
1065		c->flags |= CLIENT_STATUS;
1066		/* FALLTHROUGH */
1067	case MODEKEYEDIT_STARTOFLINE:
1068		if (c->prompt_index != 0) {
1069			c->prompt_index = 0;
1070			c->flags |= CLIENT_STATUS;
1071		}
1072		break;
1073	case MODEKEYEDIT_SWITCHMODEAPPENDLINE:
1074		c->flags |= CLIENT_STATUS;
1075		/* FALLTHROUGH */
1076	case MODEKEYEDIT_ENDOFLINE:
1077		if (c->prompt_index != size) {
1078			c->prompt_index = size;
1079			c->flags |= CLIENT_STATUS;
1080		}
1081		break;
1082	case MODEKEYEDIT_COMPLETE:
1083		if (*c->prompt_buffer == '\0')
1084			break;
1085
1086		idx = c->prompt_index;
1087		if (idx != 0)
1088			idx--;
1089
1090		/* Find the word we are in. */
1091		first = c->prompt_buffer + idx;
1092		while (first > c->prompt_buffer && *first != ' ')
1093			first--;
1094		while (*first == ' ')
1095			first++;
1096		last = c->prompt_buffer + idx;
1097		while (*last != '\0' && *last != ' ')
1098			last++;
1099		while (*last == ' ')
1100			last--;
1101		if (*last != '\0')
1102			last++;
1103		if (last <= first ||
1104		    ((size_t) (last - first)) > (sizeof word) - 1)
1105			break;
1106		memcpy(word, first, last - first);
1107		word[last - first] = '\0';
1108
1109		/* And try to complete it. */
1110		if ((s = status_prompt_complete(word)) == NULL)
1111			break;
1112
1113		/* Trim out word. */
1114		n = size - (last - c->prompt_buffer) + 1; /* with \0 */
1115		memmove(first, last, n);
1116		size -= last - first;
1117
1118		/* Insert the new word. */
1119		size += strlen(s);
1120		off = first - c->prompt_buffer;
1121		c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 1);
1122		first = c->prompt_buffer + off;
1123		memmove(first + strlen(s), first, n);
1124		memcpy(first, s, strlen(s));
1125
1126		c->prompt_index = (first - c->prompt_buffer) + strlen(s);
1127		free(s);
1128
1129		c->flags |= CLIENT_STATUS;
1130		break;
1131	case MODEKEYEDIT_BACKSPACE:
1132		if (c->prompt_index != 0) {
1133			if (c->prompt_index == size)
1134				c->prompt_buffer[--c->prompt_index] = '\0';
1135			else {
1136				memmove(c->prompt_buffer + c->prompt_index - 1,
1137				    c->prompt_buffer + c->prompt_index,
1138				    size + 1 - c->prompt_index);
1139				c->prompt_index--;
1140			}
1141			c->flags |= CLIENT_STATUS;
1142		}
1143		break;
1144	case MODEKEYEDIT_DELETE:
1145		if (c->prompt_index != size) {
1146			memmove(c->prompt_buffer + c->prompt_index,
1147			    c->prompt_buffer + c->prompt_index + 1,
1148			    size + 1 - c->prompt_index);
1149			c->flags |= CLIENT_STATUS;
1150		}
1151		break;
1152	case MODEKEYEDIT_DELETELINE:
1153		*c->prompt_buffer = '\0';
1154		c->prompt_index = 0;
1155		c->flags |= CLIENT_STATUS;
1156		break;
1157	case MODEKEYEDIT_DELETETOENDOFLINE:
1158		if (c->prompt_index < size) {
1159			c->prompt_buffer[c->prompt_index] = '\0';
1160			c->flags |= CLIENT_STATUS;
1161		}
1162		break;
1163	case MODEKEYEDIT_DELETEWORD:
1164		wsep = options_get_string(oo, "word-separators");
1165		idx = c->prompt_index;
1166
1167		/* Find a non-separator. */
1168		while (idx != 0) {
1169			idx--;
1170			if (!strchr(wsep, c->prompt_buffer[idx]))
1171				break;
1172		}
1173
1174		/* Find the separator at the beginning of the word. */
1175		while (idx != 0) {
1176			idx--;
1177			if (strchr(wsep, c->prompt_buffer[idx])) {
1178				/* Go back to the word. */
1179				idx++;
1180				break;
1181			}
1182		}
1183
1184		memmove(c->prompt_buffer + idx,
1185		    c->prompt_buffer + c->prompt_index,
1186		    size + 1 - c->prompt_index);
1187		memset(c->prompt_buffer + size - (c->prompt_index - idx),
1188		    '\0', c->prompt_index - idx);
1189		c->prompt_index = idx;
1190		c->flags |= CLIENT_STATUS;
1191		break;
1192	case MODEKEYEDIT_NEXTSPACE:
1193		wsep = " ";
1194		/* FALLTHROUGH */
1195	case MODEKEYEDIT_NEXTWORD:
1196		if (wsep == NULL)
1197			wsep = options_get_string(oo, "word-separators");
1198
1199		/* Find a separator. */
1200		while (c->prompt_index != size) {
1201			c->prompt_index++;
1202			if (strchr(wsep, c->prompt_buffer[c->prompt_index]))
1203				break;
1204		}
1205
1206		/* Find the word right after the separation. */
1207		while (c->prompt_index != size) {
1208			c->prompt_index++;
1209			if (!strchr(wsep, c->prompt_buffer[c->prompt_index]))
1210				break;
1211		}
1212
1213		c->flags |= CLIENT_STATUS;
1214		break;
1215	case MODEKEYEDIT_NEXTSPACEEND:
1216		wsep = " ";
1217		/* FALLTHROUGH */
1218	case MODEKEYEDIT_NEXTWORDEND:
1219		if (wsep == NULL)
1220			wsep = options_get_string(oo, "word-separators");
1221
1222		/* Find a word. */
1223		while (c->prompt_index != size) {
1224			c->prompt_index++;
1225			if (!strchr(wsep, c->prompt_buffer[c->prompt_index]))
1226				break;
1227		}
1228
1229		/* Find the separator at the end of the word. */
1230		while (c->prompt_index != size) {
1231			c->prompt_index++;
1232			if (strchr(wsep, c->prompt_buffer[c->prompt_index]))
1233				break;
1234		}
1235
1236		c->flags |= CLIENT_STATUS;
1237		break;
1238	case MODEKEYEDIT_PREVIOUSSPACE:
1239		wsep = " ";
1240		/* FALLTHROUGH */
1241	case MODEKEYEDIT_PREVIOUSWORD:
1242		if (wsep == NULL)
1243			wsep = options_get_string(oo, "word-separators");
1244
1245		/* Find a non-separator. */
1246		while (c->prompt_index != 0) {
1247			c->prompt_index--;
1248			if (!strchr(wsep, c->prompt_buffer[c->prompt_index]))
1249				break;
1250		}
1251
1252		/* Find the separator at the beginning of the word. */
1253		while (c->prompt_index != 0) {
1254			c->prompt_index--;
1255			if (strchr(wsep, c->prompt_buffer[c->prompt_index])) {
1256				/* Go back to the word. */
1257				c->prompt_index++;
1258				break;
1259			}
1260		}
1261
1262		c->flags |= CLIENT_STATUS;
1263		break;
1264	case MODEKEYEDIT_HISTORYUP:
1265		histstr = status_prompt_up_history(&c->prompt_hindex);
1266		if (histstr == NULL)
1267			break;
1268		free(c->prompt_buffer);
1269		c->prompt_buffer = xstrdup(histstr);
1270		c->prompt_index = strlen(c->prompt_buffer);
1271		c->flags |= CLIENT_STATUS;
1272		break;
1273	case MODEKEYEDIT_HISTORYDOWN:
1274		histstr = status_prompt_down_history(&c->prompt_hindex);
1275		if (histstr == NULL)
1276			break;
1277		free(c->prompt_buffer);
1278		c->prompt_buffer = xstrdup(histstr);
1279		c->prompt_index = strlen(c->prompt_buffer);
1280		c->flags |= CLIENT_STATUS;
1281		break;
1282	case MODEKEYEDIT_PASTE:
1283		if ((pb = paste_get_top(&global_buffers)) == NULL)
1284			break;
1285		for (n = 0; n < pb->size; n++) {
1286			ch = (u_char) pb->data[n];
1287			if (ch < 32 || ch == 127)
1288				break;
1289		}
1290
1291		c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + n + 1);
1292		if (c->prompt_index == size) {
1293			memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
1294			c->prompt_index += n;
1295			c->prompt_buffer[c->prompt_index] = '\0';
1296		} else {
1297			memmove(c->prompt_buffer + c->prompt_index + n,
1298			    c->prompt_buffer + c->prompt_index,
1299			    size + 1 - c->prompt_index);
1300			memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
1301			c->prompt_index += n;
1302		}
1303
1304		c->flags |= CLIENT_STATUS;
1305		break;
1306	case MODEKEYEDIT_TRANSPOSECHARS:
1307		idx = c->prompt_index;
1308		if (idx < size)
1309			idx++;
1310		if (idx >= 2) {
1311			swapc = c->prompt_buffer[idx - 2];
1312			c->prompt_buffer[idx - 2] = c->prompt_buffer[idx - 1];
1313			c->prompt_buffer[idx - 1] = swapc;
1314			c->prompt_index = idx;
1315			c->flags |= CLIENT_STATUS;
1316		}
1317		break;
1318	case MODEKEYEDIT_ENTER:
1319		if (*c->prompt_buffer != '\0')
1320			status_prompt_add_history(c->prompt_buffer);
1321		if (c->prompt_callbackfn(c->prompt_data, c->prompt_buffer) == 0)
1322			status_prompt_clear(c);
1323		break;
1324	case MODEKEYEDIT_CANCEL:
1325		if (c->prompt_callbackfn(c->prompt_data, NULL) == 0)
1326			status_prompt_clear(c);
1327		break;
1328	case MODEKEY_OTHER:
1329		if ((key & 0xff00) != 0 || key < 32 || key == 127)
1330			break;
1331		c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 2);
1332
1333		if (c->prompt_index == size) {
1334			c->prompt_buffer[c->prompt_index++] = key;
1335			c->prompt_buffer[c->prompt_index] = '\0';
1336		} else {
1337			memmove(c->prompt_buffer + c->prompt_index + 1,
1338			    c->prompt_buffer + c->prompt_index,
1339			    size + 1 - c->prompt_index);
1340			c->prompt_buffer[c->prompt_index++] = key;
1341		}
1342
1343		if (c->prompt_flags & PROMPT_SINGLE) {
1344			if (c->prompt_callbackfn(
1345			    c->prompt_data, c->prompt_buffer) == 0)
1346				status_prompt_clear(c);
1347		}
1348
1349		c->flags |= CLIENT_STATUS;
1350		break;
1351	default:
1352		break;
1353	}
1354}
1355
1356/* Get previous line from the history. */
1357const char *
1358status_prompt_up_history(u_int *idx)
1359{
1360	u_int size;
1361
1362	/*
1363	 * History runs from 0 to size - 1.
1364	 *
1365	 * Index is from 0 to size. Zero is empty.
1366	 */
1367
1368	size = ARRAY_LENGTH(&status_prompt_history);
1369	if (size == 0 || *idx == size)
1370		return (NULL);
1371	(*idx)++;
1372	return (ARRAY_ITEM(&status_prompt_history, size - *idx));
1373}
1374
1375/* Get next line from the history. */
1376const char *
1377status_prompt_down_history(u_int *idx)
1378{
1379	u_int size;
1380
1381	size = ARRAY_LENGTH(&status_prompt_history);
1382	if (size == 0 || *idx == 0)
1383		return ("");
1384	(*idx)--;
1385	if (*idx == 0)
1386		return ("");
1387	return (ARRAY_ITEM(&status_prompt_history, size - *idx));
1388}
1389
1390/* Add line to the history. */
1391void
1392status_prompt_add_history(const char *line)
1393{
1394	u_int size;
1395
1396	size = ARRAY_LENGTH(&status_prompt_history);
1397	if (size > 0 && strcmp(ARRAY_LAST(&status_prompt_history), line) == 0)
1398		return;
1399
1400	if (size == PROMPT_HISTORY) {
1401		free(ARRAY_FIRST(&status_prompt_history));
1402		ARRAY_REMOVE(&status_prompt_history, 0);
1403	}
1404
1405	ARRAY_ADD(&status_prompt_history, xstrdup(line));
1406}
1407
1408/* Complete word. */
1409char *
1410status_prompt_complete(const char *s)
1411{
1412	const struct cmd_entry 	  	       **cmdent;
1413	const struct options_table_entry	*oe;
1414	ARRAY_DECL(, const char *)		 list;
1415	char					*prefix, *s2;
1416	u_int					 i;
1417	size_t				 	 j;
1418
1419	if (*s == '\0')
1420		return (NULL);
1421
1422	/* First, build a list of all the possible matches. */
1423	ARRAY_INIT(&list);
1424	for (cmdent = cmd_table; *cmdent != NULL; cmdent++) {
1425		if (strncmp((*cmdent)->name, s, strlen(s)) == 0)
1426			ARRAY_ADD(&list, (*cmdent)->name);
1427	}
1428	for (oe = server_options_table; oe->name != NULL; oe++) {
1429		if (strncmp(oe->name, s, strlen(s)) == 0)
1430			ARRAY_ADD(&list, oe->name);
1431	}
1432	for (oe = session_options_table; oe->name != NULL; oe++) {
1433		if (strncmp(oe->name, s, strlen(s)) == 0)
1434			ARRAY_ADD(&list, oe->name);
1435	}
1436	for (oe = window_options_table; oe->name != NULL; oe++) {
1437		if (strncmp(oe->name, s, strlen(s)) == 0)
1438			ARRAY_ADD(&list, oe->name);
1439	}
1440
1441	/* If none, bail now. */
1442	if (ARRAY_LENGTH(&list) == 0) {
1443		ARRAY_FREE(&list);
1444		return (NULL);
1445	}
1446
1447	/* If an exact match, return it, with a trailing space. */
1448	if (ARRAY_LENGTH(&list) == 1) {
1449		xasprintf(&s2, "%s ", ARRAY_FIRST(&list));
1450		ARRAY_FREE(&list);
1451		return (s2);
1452	}
1453
1454	/* Now loop through the list and find the longest common prefix. */
1455	prefix = xstrdup(ARRAY_FIRST(&list));
1456	for (i = 1; i < ARRAY_LENGTH(&list); i++) {
1457		s = ARRAY_ITEM(&list, i);
1458
1459		j = strlen(s);
1460		if (j > strlen(prefix))
1461			j = strlen(prefix);
1462		for (; j > 0; j--) {
1463			if (prefix[j - 1] != s[j - 1])
1464				prefix[j - 1] = '\0';
1465		}
1466	}
1467
1468	ARRAY_FREE(&list);
1469	return (prefix);
1470}
1471