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