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