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