status.c revision 1.95
1/* $OpenBSD: status.c,v 1.95 2012/11/27 13:52:23 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	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
478do_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
490out:
491	free(freeptr);
492	return;
493
494skip_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. */
506char *
507status_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. */
541char *
542status_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. */
606void
607status_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. */
624void
625status_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. */
636void
637status_job_free(void *data)
638{
639	struct client	*c = data;
640
641	c->references--;
642}
643
644/* Job has finished: save its result. */
645void
646status_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. */
676char *
677status_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	if (wl == TAILQ_FIRST(&s->lastw)) {
709		fg = options_get_number(oo, "window-status-last-fg");
710		if (fg != 8)
711			colour_set_fg(gc, fg);
712		bg = options_get_number(oo, "window-status-last-bg");
713		if (bg != 8)
714			colour_set_bg(gc, bg);
715		attr = options_get_number(oo, "window-status-last-attr");
716		if (attr != 0)
717			gc->attr = attr;
718	}
719
720	if (wl->flags & WINLINK_BELL) {
721		fg = options_get_number(oo, "window-status-bell-fg");
722		if (fg != 8)
723			colour_set_fg(gc, fg);
724		bg = options_get_number(oo, "window-status-bell-bg");
725		if (bg != 8)
726			colour_set_bg(gc, bg);
727		attr = options_get_number(oo, "window-status-bell-attr");
728		if (attr != 0)
729			gc->attr = attr;
730	} else if (wl->flags & WINLINK_CONTENT) {
731		fg = options_get_number(oo, "window-status-content-fg");
732		if (fg != 8)
733			colour_set_fg(gc, fg);
734		bg = options_get_number(oo, "window-status-content-bg");
735		if (bg != 8)
736			colour_set_bg(gc, bg);
737		attr = options_get_number(oo, "window-status-content-attr");
738		if (attr != 0)
739			gc->attr = attr;
740	} else if (wl->flags & (WINLINK_ACTIVITY|WINLINK_SILENCE)) {
741		fg = options_get_number(oo, "window-status-activity-fg");
742		if (fg != 8)
743			colour_set_fg(gc, fg);
744		bg = options_get_number(oo, "window-status-activity-bg");
745		if (bg != 8)
746			colour_set_bg(gc, bg);
747		attr = options_get_number(oo, "window-status-activity-attr");
748		if (attr != 0)
749			gc->attr = attr;
750	}
751
752	text = status_replace(c, NULL, wl, NULL, fmt, t, 1);
753	return (text);
754}
755
756/* Set a status line message. */
757void printflike2
758status_message_set(struct client *c, const char *fmt, ...)
759{
760	struct timeval		 tv;
761	struct session		*s = c->session;
762	struct message_entry	*msg;
763	va_list			 ap;
764	int			 delay;
765	u_int			 i, limit;
766
767	status_prompt_clear(c);
768	status_message_clear(c);
769
770	va_start(ap, fmt);
771	xvasprintf(&c->message_string, fmt, ap);
772	va_end(ap);
773
774	ARRAY_EXPAND(&c->message_log, 1);
775	msg = &ARRAY_LAST(&c->message_log);
776	msg->msg_time = time(NULL);
777	msg->msg = xstrdup(c->message_string);
778
779	if (s == NULL)
780		limit = 0;
781	else
782		limit = options_get_number(&s->options, "message-limit");
783	if (ARRAY_LENGTH(&c->message_log) > limit) {
784		limit = ARRAY_LENGTH(&c->message_log) - limit;
785		for (i = 0; i < limit; i++) {
786			msg = &ARRAY_FIRST(&c->message_log);
787			free(msg->msg);
788			ARRAY_REMOVE(&c->message_log, 0);
789		}
790	}
791
792	delay = options_get_number(&c->session->options, "display-time");
793	tv.tv_sec = delay / 1000;
794	tv.tv_usec = (delay % 1000) * 1000L;
795
796	if (event_initialized (&c->message_timer))
797		evtimer_del(&c->message_timer);
798	evtimer_set(&c->message_timer, status_message_callback, c);
799	evtimer_add(&c->message_timer, &tv);
800
801	c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
802	c->flags |= CLIENT_STATUS;
803}
804
805/* Clear status line message. */
806void
807status_message_clear(struct client *c)
808{
809	if (c->message_string == NULL)
810		return;
811
812	free(c->message_string);
813	c->message_string = NULL;
814
815	c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
816	c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
817
818	screen_reinit(&c->status);
819}
820
821/* Clear status line message after timer expires. */
822/* ARGSUSED */
823void
824status_message_callback(unused int fd, unused short event, void *data)
825{
826	struct client	*c = data;
827
828	status_message_clear(c);
829}
830
831/* Draw client message on status line of present else on last line. */
832int
833status_message_redraw(struct client *c)
834{
835	struct screen_write_ctx		ctx;
836	struct session		       *s = c->session;
837	struct screen		        old_status;
838	size_t			        len;
839	struct grid_cell		gc;
840	int				utf8flag;
841
842	if (c->tty.sx == 0 || c->tty.sy == 0)
843		return (0);
844	memcpy(&old_status, &c->status, sizeof old_status);
845	screen_init(&c->status, c->tty.sx, 1, 0);
846
847	utf8flag = options_get_number(&s->options, "status-utf8");
848
849	len = screen_write_strlen(utf8flag, "%s", c->message_string);
850	if (len > c->tty.sx)
851		len = c->tty.sx;
852
853	memcpy(&gc, &grid_default_cell, sizeof gc);
854	colour_set_fg(&gc, options_get_number(&s->options, "message-fg"));
855	colour_set_bg(&gc, options_get_number(&s->options, "message-bg"));
856	gc.attr |= options_get_number(&s->options, "message-attr");
857
858	screen_write_start(&ctx, NULL, &c->status);
859
860	screen_write_cursormove(&ctx, 0, 0);
861	screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->message_string);
862	for (; len < c->tty.sx; len++)
863		screen_write_putc(&ctx, &gc, ' ');
864
865	screen_write_stop(&ctx);
866
867	if (grid_compare(c->status.grid, old_status.grid) == 0) {
868		screen_free(&old_status);
869		return (0);
870	}
871	screen_free(&old_status);
872	return (1);
873}
874
875/* Enable status line prompt. */
876void
877status_prompt_set(struct client *c, const char *msg, const char *input,
878    int (*callbackfn)(void *, const char *), void (*freefn)(void *),
879    void *data, int flags)
880{
881	int	keys;
882
883	status_message_clear(c);
884	status_prompt_clear(c);
885
886	c->prompt_string = status_replace(c, NULL, NULL, NULL, msg,
887	    time(NULL), 0);
888
889	c->prompt_buffer = status_replace(c, NULL, NULL, NULL, input,
890	    time(NULL), 0);
891	c->prompt_index = strlen(c->prompt_buffer);
892
893	c->prompt_callbackfn = callbackfn;
894	c->prompt_freefn = freefn;
895	c->prompt_data = data;
896
897	c->prompt_hindex = 0;
898
899	c->prompt_flags = flags;
900
901	keys = options_get_number(&c->session->options, "status-keys");
902	if (keys == MODEKEY_EMACS)
903		mode_key_init(&c->prompt_mdata, &mode_key_tree_emacs_edit);
904	else
905		mode_key_init(&c->prompt_mdata, &mode_key_tree_vi_edit);
906
907	c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
908	c->flags |= CLIENT_STATUS;
909}
910
911/* Remove status line prompt. */
912void
913status_prompt_clear(struct client *c)
914{
915	if (c->prompt_string == NULL)
916		return;
917
918	if (c->prompt_freefn != NULL && c->prompt_data != NULL)
919		c->prompt_freefn(c->prompt_data);
920
921	free(c->prompt_string);
922	c->prompt_string = NULL;
923
924	free(c->prompt_buffer);
925	c->prompt_buffer = NULL;
926
927	c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
928	c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
929
930	screen_reinit(&c->status);
931}
932
933/* Update status line prompt with a new prompt string. */
934void
935status_prompt_update(struct client *c, const char *msg, const char *input)
936{
937	free(c->prompt_string);
938	c->prompt_string = status_replace(c, NULL, NULL, NULL, msg,
939	    time(NULL), 0);
940
941	free(c->prompt_buffer);
942	c->prompt_buffer = status_replace(c, NULL, NULL, NULL, input,
943	    time(NULL), 0);
944	c->prompt_index = strlen(c->prompt_buffer);
945
946	c->prompt_hindex = 0;
947
948	c->flags |= CLIENT_STATUS;
949}
950
951/* Draw client prompt on status line of present else on last line. */
952int
953status_prompt_redraw(struct client *c)
954{
955	struct screen_write_ctx		ctx;
956	struct session		       *s = c->session;
957	struct screen		        old_status;
958	size_t			        i, size, left, len, off;
959	struct grid_cell		gc, *gcp;
960	int				utf8flag;
961
962	if (c->tty.sx == 0 || c->tty.sy == 0)
963		return (0);
964	memcpy(&old_status, &c->status, sizeof old_status);
965	screen_init(&c->status, c->tty.sx, 1, 0);
966
967	utf8flag = options_get_number(&s->options, "status-utf8");
968
969	len = screen_write_strlen(utf8flag, "%s", c->prompt_string);
970	if (len > c->tty.sx)
971		len = c->tty.sx;
972	off = 0;
973
974	memcpy(&gc, &grid_default_cell, sizeof gc);
975	/* Change colours for command mode. */
976	if (c->prompt_mdata.mode == 1) {
977		colour_set_fg(&gc, options_get_number(&s->options, "message-command-fg"));
978		colour_set_bg(&gc, options_get_number(&s->options, "message-command-bg"));
979		gc.attr |= options_get_number(&s->options, "message-command-attr");
980	} else {
981		colour_set_fg(&gc, options_get_number(&s->options, "message-fg"));
982		colour_set_bg(&gc, options_get_number(&s->options, "message-bg"));
983		gc.attr |= options_get_number(&s->options, "message-attr");
984	}
985
986	screen_write_start(&ctx, NULL, &c->status);
987
988	screen_write_cursormove(&ctx, 0, 0);
989	screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->prompt_string);
990
991	left = c->tty.sx - len;
992	if (left != 0) {
993		size = screen_write_strlen(utf8flag, "%s", c->prompt_buffer);
994		if (c->prompt_index >= left) {
995			off = c->prompt_index - left + 1;
996			if (c->prompt_index == size)
997				left--;
998			size = left;
999		}
1000		screen_write_nputs(
1001		    &ctx, left, &gc, utf8flag, "%s", c->prompt_buffer + off);
1002
1003		for (i = len + size; i < c->tty.sx; i++)
1004			screen_write_putc(&ctx, &gc, ' ');
1005	}
1006
1007	screen_write_stop(&ctx);
1008
1009	/* Apply fake cursor. */
1010	off = len + c->prompt_index - off;
1011	gcp = grid_view_get_cell(c->status.grid, off, 0);
1012	gcp->attr ^= GRID_ATTR_REVERSE;
1013
1014	if (grid_compare(c->status.grid, old_status.grid) == 0) {
1015		screen_free(&old_status);
1016		return (0);
1017	}
1018	screen_free(&old_status);
1019	return (1);
1020}
1021
1022/* Handle keys in prompt. */
1023void
1024status_prompt_key(struct client *c, int key)
1025{
1026	struct session		*sess = c->session;
1027	struct options		*oo = &sess->options;
1028	struct paste_buffer	*pb;
1029	char			*s, *first, *last, word[64], swapc;
1030	const char		*histstr;
1031	const char		*wsep = NULL;
1032	u_char			 ch;
1033	size_t			 size, n, off, idx;
1034
1035	size = strlen(c->prompt_buffer);
1036	switch (mode_key_lookup(&c->prompt_mdata, key)) {
1037	case MODEKEYEDIT_CURSORLEFT:
1038		if (c->prompt_index > 0) {
1039			c->prompt_index--;
1040			c->flags |= CLIENT_STATUS;
1041		}
1042		break;
1043	case MODEKEYEDIT_SWITCHMODE:
1044		c->flags |= CLIENT_STATUS;
1045		break;
1046	case MODEKEYEDIT_SWITCHMODEAPPEND:
1047		c->flags |= CLIENT_STATUS;
1048		/* FALLTHROUGH */
1049	case MODEKEYEDIT_CURSORRIGHT:
1050		if (c->prompt_index < size) {
1051			c->prompt_index++;
1052			c->flags |= CLIENT_STATUS;
1053		}
1054		break;
1055	case MODEKEYEDIT_SWITCHMODEBEGINLINE:
1056		c->flags |= CLIENT_STATUS;
1057		/* FALLTHROUGH */
1058	case MODEKEYEDIT_STARTOFLINE:
1059		if (c->prompt_index != 0) {
1060			c->prompt_index = 0;
1061			c->flags |= CLIENT_STATUS;
1062		}
1063		break;
1064	case MODEKEYEDIT_SWITCHMODEAPPENDLINE:
1065		c->flags |= CLIENT_STATUS;
1066		/* FALLTHROUGH */
1067	case MODEKEYEDIT_ENDOFLINE:
1068		if (c->prompt_index != size) {
1069			c->prompt_index = size;
1070			c->flags |= CLIENT_STATUS;
1071		}
1072		break;
1073	case MODEKEYEDIT_COMPLETE:
1074		if (*c->prompt_buffer == '\0')
1075			break;
1076
1077		idx = c->prompt_index;
1078		if (idx != 0)
1079			idx--;
1080
1081		/* Find the word we are in. */
1082		first = c->prompt_buffer + idx;
1083		while (first > c->prompt_buffer && *first != ' ')
1084			first--;
1085		while (*first == ' ')
1086			first++;
1087		last = c->prompt_buffer + idx;
1088		while (*last != '\0' && *last != ' ')
1089			last++;
1090		while (*last == ' ')
1091			last--;
1092		if (*last != '\0')
1093			last++;
1094		if (last <= first ||
1095		    ((size_t) (last - first)) > (sizeof word) - 1)
1096			break;
1097		memcpy(word, first, last - first);
1098		word[last - first] = '\0';
1099
1100		/* And try to complete it. */
1101		if ((s = status_prompt_complete(word)) == NULL)
1102			break;
1103
1104		/* Trim out word. */
1105		n = size - (last - c->prompt_buffer) + 1; /* with \0 */
1106		memmove(first, last, n);
1107		size -= last - first;
1108
1109		/* Insert the new word. */
1110		size += strlen(s);
1111		off = first - c->prompt_buffer;
1112		c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 1);
1113		first = c->prompt_buffer + off;
1114		memmove(first + strlen(s), first, n);
1115		memcpy(first, s, strlen(s));
1116
1117		c->prompt_index = (first - c->prompt_buffer) + strlen(s);
1118		free(s);
1119
1120		c->flags |= CLIENT_STATUS;
1121		break;
1122	case MODEKEYEDIT_BACKSPACE:
1123		if (c->prompt_index != 0) {
1124			if (c->prompt_index == size)
1125				c->prompt_buffer[--c->prompt_index] = '\0';
1126			else {
1127				memmove(c->prompt_buffer + c->prompt_index - 1,
1128				    c->prompt_buffer + c->prompt_index,
1129				    size + 1 - c->prompt_index);
1130				c->prompt_index--;
1131			}
1132			c->flags |= CLIENT_STATUS;
1133		}
1134		break;
1135	case MODEKEYEDIT_DELETE:
1136		if (c->prompt_index != size) {
1137			memmove(c->prompt_buffer + c->prompt_index,
1138			    c->prompt_buffer + c->prompt_index + 1,
1139			    size + 1 - c->prompt_index);
1140			c->flags |= CLIENT_STATUS;
1141		}
1142		break;
1143	case MODEKEYEDIT_DELETELINE:
1144		*c->prompt_buffer = '\0';
1145		c->prompt_index = 0;
1146		c->flags |= CLIENT_STATUS;
1147		break;
1148	case MODEKEYEDIT_DELETETOENDOFLINE:
1149		if (c->prompt_index < size) {
1150			c->prompt_buffer[c->prompt_index] = '\0';
1151			c->flags |= CLIENT_STATUS;
1152		}
1153		break;
1154	case MODEKEYEDIT_DELETEWORD:
1155		wsep = options_get_string(oo, "word-separators");
1156		idx = c->prompt_index;
1157
1158		/* Find a non-separator. */
1159		while (idx != 0) {
1160			idx--;
1161			if (!strchr(wsep, c->prompt_buffer[idx]))
1162				break;
1163		}
1164
1165		/* Find the separator at the beginning of the word. */
1166		while (idx != 0) {
1167			idx--;
1168			if (strchr(wsep, c->prompt_buffer[idx])) {
1169				/* Go back to the word. */
1170				idx++;
1171				break;
1172			}
1173		}
1174
1175		memmove(c->prompt_buffer + idx,
1176		    c->prompt_buffer + c->prompt_index,
1177		    size + 1 - c->prompt_index);
1178		memset(c->prompt_buffer + size - (c->prompt_index - idx),
1179		    '\0', c->prompt_index - idx);
1180		c->prompt_index = idx;
1181		c->flags |= CLIENT_STATUS;
1182		break;
1183	case MODEKEYEDIT_NEXTSPACE:
1184		wsep = " ";
1185		/* FALLTHROUGH */
1186	case MODEKEYEDIT_NEXTWORD:
1187		if (wsep == NULL)
1188			wsep = options_get_string(oo, "word-separators");
1189
1190		/* Find a separator. */
1191		while (c->prompt_index != size) {
1192			c->prompt_index++;
1193			if (strchr(wsep, c->prompt_buffer[c->prompt_index]))
1194				break;
1195		}
1196
1197		/* Find the word right after the separation. */
1198		while (c->prompt_index != size) {
1199			c->prompt_index++;
1200			if (!strchr(wsep, c->prompt_buffer[c->prompt_index]))
1201				break;
1202		}
1203
1204		c->flags |= CLIENT_STATUS;
1205		break;
1206	case MODEKEYEDIT_NEXTSPACEEND:
1207		wsep = " ";
1208		/* FALLTHROUGH */
1209	case MODEKEYEDIT_NEXTWORDEND:
1210		if (wsep == NULL)
1211			wsep = options_get_string(oo, "word-separators");
1212
1213		/* Find a word. */
1214		while (c->prompt_index != size) {
1215			c->prompt_index++;
1216			if (!strchr(wsep, c->prompt_buffer[c->prompt_index]))
1217				break;
1218		}
1219
1220		/* Find the separator at the end of the word. */
1221		while (c->prompt_index != size) {
1222			c->prompt_index++;
1223			if (strchr(wsep, c->prompt_buffer[c->prompt_index]))
1224				break;
1225		}
1226
1227		c->flags |= CLIENT_STATUS;
1228		break;
1229	case MODEKEYEDIT_PREVIOUSSPACE:
1230		wsep = " ";
1231		/* FALLTHROUGH */
1232	case MODEKEYEDIT_PREVIOUSWORD:
1233		if (wsep == NULL)
1234			wsep = options_get_string(oo, "word-separators");
1235
1236		/* Find a non-separator. */
1237		while (c->prompt_index != 0) {
1238			c->prompt_index--;
1239			if (!strchr(wsep, c->prompt_buffer[c->prompt_index]))
1240				break;
1241		}
1242
1243		/* Find the separator at the beginning of the word. */
1244		while (c->prompt_index != 0) {
1245			c->prompt_index--;
1246			if (strchr(wsep, c->prompt_buffer[c->prompt_index])) {
1247				/* Go back to the word. */
1248				c->prompt_index++;
1249				break;
1250			}
1251		}
1252
1253		c->flags |= CLIENT_STATUS;
1254		break;
1255	case MODEKEYEDIT_HISTORYUP:
1256		histstr = status_prompt_up_history(&c->prompt_hindex);
1257		if (histstr == NULL)
1258			break;
1259		free(c->prompt_buffer);
1260		c->prompt_buffer = xstrdup(histstr);
1261		c->prompt_index = strlen(c->prompt_buffer);
1262		c->flags |= CLIENT_STATUS;
1263		break;
1264	case MODEKEYEDIT_HISTORYDOWN:
1265		histstr = status_prompt_down_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_PASTE:
1274		if ((pb = paste_get_top(&global_buffers)) == NULL)
1275			break;
1276		for (n = 0; n < pb->size; n++) {
1277			ch = (u_char) pb->data[n];
1278			if (ch < 32 || ch == 127)
1279				break;
1280		}
1281
1282		c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + n + 1);
1283		if (c->prompt_index == size) {
1284			memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
1285			c->prompt_index += n;
1286			c->prompt_buffer[c->prompt_index] = '\0';
1287		} else {
1288			memmove(c->prompt_buffer + c->prompt_index + n,
1289			    c->prompt_buffer + c->prompt_index,
1290			    size + 1 - c->prompt_index);
1291			memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
1292			c->prompt_index += n;
1293		}
1294
1295		c->flags |= CLIENT_STATUS;
1296		break;
1297	case MODEKEYEDIT_TRANSPOSECHARS:
1298		idx = c->prompt_index;
1299		if (idx < size)
1300			idx++;
1301		if (idx >= 2) {
1302			swapc = c->prompt_buffer[idx - 2];
1303			c->prompt_buffer[idx - 2] = c->prompt_buffer[idx - 1];
1304			c->prompt_buffer[idx - 1] = swapc;
1305			c->prompt_index = idx;
1306			c->flags |= CLIENT_STATUS;
1307		}
1308		break;
1309	case MODEKEYEDIT_ENTER:
1310		if (*c->prompt_buffer != '\0')
1311			status_prompt_add_history(c->prompt_buffer);
1312		if (c->prompt_callbackfn(c->prompt_data, c->prompt_buffer) == 0)
1313			status_prompt_clear(c);
1314		break;
1315	case MODEKEYEDIT_CANCEL:
1316		if (c->prompt_callbackfn(c->prompt_data, NULL) == 0)
1317			status_prompt_clear(c);
1318		break;
1319	case MODEKEY_OTHER:
1320		if ((key & 0xff00) != 0 || key < 32 || key == 127)
1321			break;
1322		c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 2);
1323
1324		if (c->prompt_index == size) {
1325			c->prompt_buffer[c->prompt_index++] = key;
1326			c->prompt_buffer[c->prompt_index] = '\0';
1327		} else {
1328			memmove(c->prompt_buffer + c->prompt_index + 1,
1329			    c->prompt_buffer + c->prompt_index,
1330			    size + 1 - c->prompt_index);
1331			c->prompt_buffer[c->prompt_index++] = key;
1332		}
1333
1334		if (c->prompt_flags & PROMPT_SINGLE) {
1335			if (c->prompt_callbackfn(
1336			    c->prompt_data, c->prompt_buffer) == 0)
1337				status_prompt_clear(c);
1338		}
1339
1340		c->flags |= CLIENT_STATUS;
1341		break;
1342	default:
1343		break;
1344	}
1345}
1346
1347/* Get previous line from the history. */
1348const char *
1349status_prompt_up_history(u_int *idx)
1350{
1351	u_int size;
1352
1353	/*
1354	 * History runs from 0 to size - 1.
1355	 *
1356	 * Index is from 0 to size. Zero is empty.
1357	 */
1358
1359	size = ARRAY_LENGTH(&status_prompt_history);
1360	if (size == 0 || *idx == size)
1361		return (NULL);
1362	(*idx)++;
1363	return (ARRAY_ITEM(&status_prompt_history, size - *idx));
1364}
1365
1366/* Get next line from the history. */
1367const char *
1368status_prompt_down_history(u_int *idx)
1369{
1370	u_int size;
1371
1372	size = ARRAY_LENGTH(&status_prompt_history);
1373	if (size == 0 || *idx == 0)
1374		return ("");
1375	(*idx)--;
1376	if (*idx == 0)
1377		return ("");
1378	return (ARRAY_ITEM(&status_prompt_history, size - *idx));
1379}
1380
1381/* Add line to the history. */
1382void
1383status_prompt_add_history(const char *line)
1384{
1385	u_int size;
1386
1387	size = ARRAY_LENGTH(&status_prompt_history);
1388	if (size > 0 && strcmp(ARRAY_LAST(&status_prompt_history), line) == 0)
1389		return;
1390
1391	if (size == PROMPT_HISTORY) {
1392		free(ARRAY_FIRST(&status_prompt_history));
1393		ARRAY_REMOVE(&status_prompt_history, 0);
1394	}
1395
1396	ARRAY_ADD(&status_prompt_history, xstrdup(line));
1397}
1398
1399/* Complete word. */
1400char *
1401status_prompt_complete(const char *s)
1402{
1403	const struct cmd_entry 	  	       **cmdent;
1404	const struct options_table_entry	*oe;
1405	ARRAY_DECL(, const char *)		 list;
1406	char					*prefix, *s2;
1407	u_int					 i;
1408	size_t				 	 j;
1409
1410	if (*s == '\0')
1411		return (NULL);
1412
1413	/* First, build a list of all the possible matches. */
1414	ARRAY_INIT(&list);
1415	for (cmdent = cmd_table; *cmdent != NULL; cmdent++) {
1416		if (strncmp((*cmdent)->name, s, strlen(s)) == 0)
1417			ARRAY_ADD(&list, (*cmdent)->name);
1418	}
1419	for (oe = server_options_table; oe->name != NULL; oe++) {
1420		if (strncmp(oe->name, s, strlen(s)) == 0)
1421			ARRAY_ADD(&list, oe->name);
1422	}
1423	for (oe = session_options_table; oe->name != NULL; oe++) {
1424		if (strncmp(oe->name, s, strlen(s)) == 0)
1425			ARRAY_ADD(&list, oe->name);
1426	}
1427	for (oe = window_options_table; oe->name != NULL; oe++) {
1428		if (strncmp(oe->name, s, strlen(s)) == 0)
1429			ARRAY_ADD(&list, oe->name);
1430	}
1431
1432	/* If none, bail now. */
1433	if (ARRAY_LENGTH(&list) == 0) {
1434		ARRAY_FREE(&list);
1435		return (NULL);
1436	}
1437
1438	/* If an exact match, return it, with a trailing space. */
1439	if (ARRAY_LENGTH(&list) == 1) {
1440		xasprintf(&s2, "%s ", ARRAY_FIRST(&list));
1441		ARRAY_FREE(&list);
1442		return (s2);
1443	}
1444
1445	/* Now loop through the list and find the longest common prefix. */
1446	prefix = xstrdup(ARRAY_FIRST(&list));
1447	for (i = 1; i < ARRAY_LENGTH(&list); i++) {
1448		s = ARRAY_ITEM(&list, i);
1449
1450		j = strlen(s);
1451		if (j > strlen(prefix))
1452			j = strlen(prefix);
1453		for (; j > 0; j--) {
1454			if (prefix[j - 1] != s[j - 1])
1455				prefix[j - 1] = '\0';
1456		}
1457	}
1458
1459	ARRAY_FREE(&list);
1460	return (prefix);
1461}
1462