status.c revision 1.6
1/* $OpenBSD: status.c,v 1.6 2009/07/12 16:15:34 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_replace(struct session *, char *, time_t);
33char   *status_replace_popen(char **);
34size_t	status_width(struct winlink *);
35char   *status_print(struct session *, struct winlink *, struct grid_cell *);
36
37void	status_prompt_add_history(struct client *);
38char   *status_prompt_complete(const char *);
39
40/* Draw status for client on the last lines of given context. */
41int
42status_redraw(struct client *c)
43{
44	struct screen_write_ctx		ctx;
45	struct session		       *s = c->session;
46	struct winlink		       *wl;
47	struct window_pane	       *wp;
48	struct screen		       *sc = NULL, old_status;
49	char		 	       *left, *right, *text, *ptr;
50	size_t				llen, llen2, rlen, rlen2, offset;
51	size_t				xx, yy, sy, size, start, width;
52	struct grid_cell	        stdgc, gc;
53	int				larrow, rarrow, utf8flag;
54
55	left = right = NULL;
56
57	/* Create the target screen. */
58	memcpy(&old_status, &c->status, sizeof old_status);
59	screen_init(&c->status, c->tty.sx, 1, 0);
60
61	/* No status line? */
62	if (c->tty.sy == 0 || !options_get_number(&s->options, "status"))
63		goto off;
64	larrow = rarrow = 0;
65
66	if (gettimeofday(&c->status_timer, NULL) != 0)
67		fatal("gettimeofday");
68	memcpy(&stdgc, &grid_default_cell, sizeof gc);
69	stdgc.bg = options_get_number(&s->options, "status-fg");
70	stdgc.fg = options_get_number(&s->options, "status-bg");
71	stdgc.attr |= options_get_number(&s->options, "status-attr");
72
73	yy = c->tty.sy - 1;
74	if (yy == 0)
75		goto blank;
76
77	/* Caring about UTF-8 in status line? */
78	utf8flag = options_get_number(&s->options, "status-utf8");
79
80	/* Work out the left and right strings. */
81	left = status_replace(s, options_get_string(
82	    &s->options, "status-left"), c->status_timer.tv_sec);
83	llen = options_get_number(&s->options, "status-left-length");
84	llen2 = screen_write_strlen(utf8flag, "%s", left);
85	if (llen2 < llen)
86		llen = llen2;
87
88	right = status_replace(s, options_get_string(
89	    &s->options, "status-right"), c->status_timer.tv_sec);
90	rlen = options_get_number(&s->options, "status-right-length");
91	rlen2 = screen_write_strlen(utf8flag, "%s", right);
92	if (rlen2 < rlen)
93		rlen = rlen2;
94
95	/*
96	 * Figure out how much space we have for the window list. If there isn't
97	 * enough space, just wimp out.
98	 */
99	xx = 0;
100	if (llen != 0)
101		xx += llen + 1;
102	if (rlen != 0)
103		xx += rlen + 1;
104	if (c->tty.sx == 0 || c->tty.sx <= xx)
105		goto blank;
106	xx = c->tty.sx - xx;
107
108	/*
109	 * Right. We have xx characters to fill. Find out how much is to go in
110	 * them and the offset of the current window (it must be on screen).
111	 */
112	width = offset = 0;
113	RB_FOREACH(wl, winlinks, &s->windows) {
114		size = status_width(wl) + 1;
115		if (wl == s->curw)
116			offset = width;
117		width += size;
118	}
119	start = 0;
120
121	/* If there is enough space for the total width, all is gravy. */
122	if (width <= xx)
123		goto draw;
124
125	/* Find size of current window text. */
126	size = status_width(s->curw);
127
128	/*
129	 * If the offset is already on screen, we're good to draw from the
130	 * start and just leave off the end.
131	 */
132	if (offset + size < xx) {
133		if (xx > 0) {
134			rarrow = 1;
135			xx--;
136		}
137
138		width = xx;
139		goto draw;
140	}
141
142	/*
143	 * Work out how many characters we need to omit from the start. There
144	 * are xx characters to fill, and offset + size must be the last. So,
145	 * the start character is offset + size - xx.
146	 */
147	if (xx > 0) {
148		larrow = 1;
149		xx--;
150	}
151
152	start = offset + size - xx;
153 	if (xx > 0 && width > start + xx + 1) { /* + 1, eh? */
154 		rarrow = 1;
155 		start++;
156 		xx--;
157 	}
158 	width = xx;
159
160draw:
161	/* Bail here if anything is too small too. XXX. */
162	if (width == 0 || xx == 0)
163		goto blank;
164
165 	/* Begin drawing and move to the starting position. */
166	screen_write_start(&ctx, NULL, &c->status);
167	if (llen != 0) {
168 		screen_write_cursormove(&ctx, 0, yy);
169		screen_write_nputs(&ctx, llen, &stdgc, utf8flag, "%s", left);
170		screen_write_putc(&ctx, &stdgc, ' ');
171		if (larrow)
172			screen_write_putc(&ctx, &stdgc, ' ');
173	} else {
174		if (larrow)
175			screen_write_cursormove(&ctx, 1, yy);
176		else
177			screen_write_cursormove(&ctx, 0, yy);
178	}
179
180	/* Draw each character in succession. */
181	offset = 0;
182	RB_FOREACH(wl, winlinks, &s->windows) {
183		memcpy(&gc, &stdgc, sizeof gc);
184		text = status_print(s, wl, &gc);
185
186		if (larrow == 1 && offset < start) {
187			if (session_alert_has(s, wl, WINDOW_ACTIVITY))
188				larrow = -1;
189			else if (session_alert_has(s, wl, WINDOW_BELL))
190				larrow = -1;
191			else if (session_alert_has(s, wl, WINDOW_CONTENT))
192				larrow = -1;
193		}
194
195 		for (ptr = text; *ptr != '\0'; ptr++) {
196			if (offset >= start && offset < start + width)
197				screen_write_putc(&ctx, &gc, *ptr);
198			offset++;
199		}
200
201		if (rarrow == 1 && offset > start + width) {
202			if (session_alert_has(s, wl, WINDOW_ACTIVITY))
203				rarrow = -1;
204			else if (session_alert_has(s, wl, WINDOW_BELL))
205				rarrow = -1;
206			else if (session_alert_has(s, wl, WINDOW_CONTENT))
207				rarrow = -1;
208		}
209
210		if (offset < start + width) {
211			if (offset >= start) {
212				screen_write_putc(&ctx, &stdgc, ' ');
213			}
214			offset++;
215		}
216
217		xfree(text);
218	}
219
220	/* Fill the remaining space if any. */
221 	while (offset++ < xx)
222		screen_write_putc(&ctx, &stdgc, ' ');
223
224	/* Draw the last item. */
225	if (rlen != 0) {
226		screen_write_cursormove(&ctx, c->tty.sx - rlen - 1, yy);
227		screen_write_putc(&ctx, &stdgc, ' ');
228		screen_write_nputs(&ctx, rlen, &stdgc, utf8flag, "%s", right);
229	}
230
231	/* Draw the arrows. */
232	if (larrow != 0) {
233		memcpy(&gc, &stdgc, sizeof gc);
234		if (larrow == -1)
235			gc.attr ^= GRID_ATTR_REVERSE;
236		if (llen != 0)
237			screen_write_cursormove(&ctx, llen + 1, yy);
238		else
239			screen_write_cursormove(&ctx, 0, yy);
240		screen_write_putc(&ctx, &gc, '<');
241	}
242	if (rarrow != 0) {
243		memcpy(&gc, &stdgc, sizeof gc);
244		if (rarrow == -1)
245			gc.attr ^= GRID_ATTR_REVERSE;
246		if (rlen != 0)
247			screen_write_cursormove(&ctx, c->tty.sx - rlen - 2, yy);
248		else
249			screen_write_cursormove(&ctx, c->tty.sx - 1, yy);
250		screen_write_putc(&ctx, &gc, '>');
251	}
252
253	goto out;
254
255blank:
256 	/* Just draw the whole line as blank. */
257	screen_write_start(&ctx, NULL, &c->status);
258	screen_write_cursormove(&ctx, 0, yy);
259	for (offset = 0; offset < c->tty.sx; offset++)
260		screen_write_putc(&ctx, &stdgc, ' ');
261
262	goto out;
263
264off:
265	/*
266	 * Draw the real window last line. Necessary to wipe over message if
267	 * status is off. Not sure this is the right place for this.
268	 */
269	memcpy(&stdgc, &grid_default_cell, sizeof stdgc);
270	screen_write_start(&ctx, NULL, &c->status);
271
272	sy = 0;
273	TAILQ_FOREACH(wp, &s->curw->window->panes, entry) {
274		sy += wp->sy + 1;
275		sc = wp->screen;
276	}
277
278	screen_write_cursormove(&ctx, 0, 0);
279	if (sy < c->tty.sy) {
280		/* If the screen is too small, use blank. */
281 		for (offset = 0; offset < c->tty.sx; offset++)
282 			screen_write_putc(&ctx, &stdgc, ' ');
283	} else {
284		screen_write_copy(&ctx,
285		    sc, 0, sc->grid->hsize + screen_size_y(sc) - 1, c->tty.sx, 1);
286	}
287
288out:
289	screen_write_stop(&ctx);
290
291	if (left != NULL)
292		xfree(left);
293	if (right != NULL)
294		xfree(right);
295
296	if (grid_compare(c->status.grid, old_status.grid) == 0) {
297		screen_free(&old_status);
298		return (0);
299	}
300	screen_free(&old_status);
301	return (1);
302}
303
304char *
305status_replace(struct session *s, char *fmt, time_t t)
306{
307	struct winlink *wl = s->curw;
308	static char	out[BUFSIZ];
309	char		in[BUFSIZ], tmp[256], ch, *iptr, *optr, *ptr, *endptr;
310	char           *savedptr;
311	size_t		len;
312	long		n;
313
314	strftime(in, sizeof in, fmt, localtime(&t));
315	in[(sizeof in) - 1] = '\0';
316
317	iptr = in;
318	optr = out;
319	savedptr = NULL;
320
321	while (*iptr != '\0') {
322		if (optr >= out + (sizeof out) - 1)
323			break;
324		switch (ch = *iptr++) {
325		case '#':
326			errno = 0;
327			n = strtol(iptr, &endptr, 10);
328			if ((n == 0 && errno != EINVAL) ||
329			    (n == LONG_MIN && errno != ERANGE) ||
330			    (n == LONG_MAX && errno != ERANGE) ||
331			    n != 0)
332				iptr = endptr;
333			if (n <= 0)
334				n = LONG_MAX;
335
336			ptr = NULL;
337			switch (*iptr++) {
338			case '(':
339				if (ptr == NULL) {
340					ptr = status_replace_popen(&iptr);
341					if (ptr == NULL)
342						break;
343					savedptr = ptr;
344				}
345				/* FALLTHROUGH */
346			case 'H':
347				if (ptr == NULL) {
348					if (gethostname(tmp, sizeof tmp) != 0)
349						fatal("gethostname");
350					ptr = tmp;
351				}
352				/* FALLTHROUGH */
353			case 'S':
354				if (ptr == NULL)
355					ptr = s->name;
356				/* FALLTHROUGH */
357			case 'T':
358				if (ptr == NULL)
359					ptr = wl->window->active->base.title;
360				len = strlen(ptr);
361				if ((size_t) n < len)
362					len = n;
363				if (optr + len >= out + (sizeof out) - 1)
364					break;
365				while (len > 0 && *ptr != '\0') {
366					*optr++ = *ptr++;
367					len--;
368				}
369				break;
370			case '#':
371				*optr++ = '#';
372				break;
373			}
374			if (savedptr != NULL) {
375				xfree(savedptr);
376				savedptr = NULL;
377			}
378			break;
379		default:
380			*optr++ = ch;
381			break;
382		}
383	}
384	*optr = '\0';
385
386	return (xstrdup(out));
387}
388
389char *
390status_replace_popen(char **iptr)
391{
392	FILE	*f;
393	char	*buf, *cmd, *ptr;
394	int	lastesc;
395	size_t	len;
396
397	if (**iptr == '\0')
398		return (NULL);
399	if (**iptr == ')') {		/* no command given */
400		(*iptr)++;
401		return (NULL);
402	}
403
404	buf = NULL;
405
406	cmd = xmalloc(strlen(*iptr) + 1);
407	len = 0;
408
409	lastesc = 0;
410	for (; **iptr != '\0'; (*iptr)++) {
411		if (!lastesc && **iptr == ')')
412			break;		/* unescaped ) is the end */
413		if (!lastesc && **iptr == '\\') {
414			lastesc = 1;
415			continue;	/* skip \ if not escaped */
416		}
417		lastesc = 0;
418		cmd[len++] = **iptr;
419	}
420	if (**iptr == '\0')		/* no terminating ) */
421		goto out;
422	(*iptr)++;			/* skip final ) */
423	cmd[len] = '\0';
424
425	if ((f = popen(cmd, "r")) == NULL)
426		goto out;
427
428	if ((buf = fgetln(f, &len)) == NULL) {
429		pclose(f);
430		goto out;
431	}
432	if (buf[len - 1] == '\n') {
433		buf[len - 1] = '\0';
434		buf = xstrdup(buf);
435	} else {
436		ptr = xmalloc(len + 1);
437		memcpy(ptr, buf, len);
438		ptr[len] = '\0';
439		buf = ptr;
440	}
441	pclose(f);
442
443out:
444	xfree(cmd);
445	return (buf);
446}
447
448size_t
449status_width(struct winlink *wl)
450{
451	return (xsnprintf(NULL, 0, "%d:%s ", wl->idx, wl->window->name));
452}
453
454char *
455status_print(struct session *s, struct winlink *wl, struct grid_cell *gc)
456{
457	char   *text, flag;
458	u_char	fg, bg, attr;
459
460	fg = options_get_number(&wl->window->options, "window-status-fg");
461	if (fg != 8)
462		gc->fg = fg;
463	bg = options_get_number(&wl->window->options, "window-status-bg");
464	if (bg != 8)
465		gc->bg = bg;
466	attr = options_get_number(&wl->window->options, "window-status-attr");
467	if (attr != 0)
468		gc->attr = attr;
469
470	flag = ' ';
471 	if (wl == SLIST_FIRST(&s->lastw))
472		flag = '-';
473	if (wl == s->curw)
474		flag = '*';
475
476	if (session_alert_has(s, wl, WINDOW_ACTIVITY)) {
477		flag = '#';
478		gc->attr ^= GRID_ATTR_REVERSE;
479	} else if (session_alert_has(s, wl, WINDOW_BELL)) {
480		flag = '!';
481		gc->attr ^= GRID_ATTR_REVERSE;
482	} else if (session_alert_has(s, wl, WINDOW_CONTENT)) {
483		flag = '+';
484		gc->attr ^= GRID_ATTR_REVERSE;
485	}
486
487	xasprintf(&text, "%d:%s%c", wl->idx, wl->window->name, flag);
488	return (text);
489}
490
491void
492status_message_set(struct client *c, const char *msg)
493{
494	struct timeval	tv;
495	int		delay;
496
497	delay = options_get_number(&c->session->options, "display-time");
498	tv.tv_sec = delay / 1000;
499	tv.tv_usec = (delay % 1000) * 1000L;
500
501	c->message_string = xstrdup(msg);
502	if (gettimeofday(&c->message_timer, NULL) != 0)
503		fatal("gettimeofday");
504	timeradd(&c->message_timer, &tv, &c->message_timer);
505
506	c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
507	c->flags |= CLIENT_STATUS;
508}
509
510void
511status_message_clear(struct client *c)
512{
513	if (c->message_string == NULL)
514		return;
515
516	xfree(c->message_string);
517	c->message_string = NULL;
518
519	c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
520	c->flags |= CLIENT_REDRAW;
521}
522
523/* Draw client message on status line of present else on last line. */
524int
525status_message_redraw(struct client *c)
526{
527	struct screen_write_ctx		ctx;
528	struct session		       *s = c->session;
529	struct screen		        old_status;
530	size_t			        len;
531	struct grid_cell		gc;
532
533	if (c->tty.sx == 0 || c->tty.sy == 0)
534		return (0);
535	memcpy(&old_status, &c->status, sizeof old_status);
536	screen_init(&c->status, c->tty.sx, 1, 0);
537
538	len = strlen(c->message_string);
539	if (len > c->tty.sx)
540		len = c->tty.sx;
541
542	memcpy(&gc, &grid_default_cell, sizeof gc);
543	gc.bg = options_get_number(&s->options, "message-fg");
544	gc.fg = options_get_number(&s->options, "message-bg");
545	gc.attr |= options_get_number(&s->options, "message-attr");
546
547	screen_write_start(&ctx, NULL, &c->status);
548
549	screen_write_cursormove(&ctx, 0, 0);
550	screen_write_puts(&ctx, &gc, "%.*s", (int) len, c->message_string);
551	for (; len < c->tty.sx; len++)
552		screen_write_putc(&ctx, &gc, ' ');
553
554	screen_write_stop(&ctx);
555
556	if (grid_compare(c->status.grid, old_status.grid) == 0) {
557		screen_free(&old_status);
558		return (0);
559	}
560	screen_free(&old_status);
561	return (1);
562}
563
564void
565status_prompt_set(struct client *c,
566    const char *msg, int (*fn)(void *, const char *), void *data, int flags)
567{
568	c->prompt_string = xstrdup(msg);
569
570	c->prompt_buffer = xstrdup("");
571	c->prompt_index = 0;
572
573	c->prompt_callback = fn;
574	c->prompt_data = data;
575
576	c->prompt_hindex = 0;
577
578	c->prompt_flags = flags;
579
580	mode_key_init(&c->prompt_mdata,
581	    options_get_number(&c->session->options, "status-keys"),
582	    MODEKEY_CANEDIT);
583
584	c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
585	c->flags |= CLIENT_STATUS;
586}
587
588void
589status_prompt_clear(struct client *c)
590{
591	if (c->prompt_string == NULL)
592		return;
593
594	mode_key_free(&c->prompt_mdata);
595
596	xfree(c->prompt_string);
597	c->prompt_string = NULL;
598
599	if (c->prompt_flags & PROMPT_HIDDEN)
600		memset(c->prompt_buffer, 0, strlen(c->prompt_buffer));
601	xfree(c->prompt_buffer);
602	c->prompt_buffer = NULL;
603
604	c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
605	c->flags |= CLIENT_REDRAW;
606}
607
608/* Draw client prompt on status line of present else on last line. */
609int
610status_prompt_redraw(struct client *c)
611{
612	struct screen_write_ctx	ctx;
613	struct session		       *s = c->session;
614	struct screen		        old_status;
615	size_t			        i, size, left, len, offset, n;
616	char				ch;
617	struct grid_cell		gc;
618
619	if (c->tty.sx == 0 || c->tty.sy == 0)
620		return (0);
621	memcpy(&old_status, &c->status, sizeof old_status);
622	screen_init(&c->status, c->tty.sx, 1, 0);
623	offset = 0;
624
625	len = strlen(c->prompt_string);
626	if (len > c->tty.sx)
627		len = c->tty.sx;
628
629	memcpy(&gc, &grid_default_cell, sizeof gc);
630	gc.bg = options_get_number(&s->options, "message-fg");
631	gc.fg = options_get_number(&s->options, "message-bg");
632	gc.attr |= options_get_number(&s->options, "message-attr");
633
634	screen_write_start(&ctx, NULL, &c->status);
635
636	screen_write_cursormove(&ctx, 0, 0);
637	screen_write_puts(&ctx, &gc, "%.*s", (int) len, c->prompt_string);
638
639	left = c->tty.sx - len;
640	if (left != 0) {
641		if (c->prompt_index < left)
642			size = strlen(c->prompt_buffer);
643		else {
644			offset = c->prompt_index - left - 1;
645			if (c->prompt_index == strlen(c->prompt_buffer))
646				left--;
647			size = left;
648		}
649		if (c->prompt_flags & PROMPT_HIDDEN) {
650			n = strlen(c->prompt_buffer);
651			if (n > left)
652				n = left;
653			for (i = 0; i < n; i++)
654				screen_write_putc(&ctx, &gc, '*');
655		} else {
656			screen_write_puts(&ctx, &gc,
657			    "%.*s", (int) left, c->prompt_buffer + offset);
658		}
659
660		for (i = len + size; i < c->tty.sx; i++)
661			screen_write_putc(&ctx, &gc, ' ');
662	}
663
664	/* Draw a fake cursor. */
665	screen_write_cursormove(&ctx, len + c->prompt_index - offset, 0);
666	if (c->prompt_index == strlen(c->prompt_buffer))
667		ch = ' ';
668	else {
669		if (c->prompt_flags & PROMPT_HIDDEN)
670			ch = '*';
671		else
672			ch = c->prompt_buffer[c->prompt_index];
673	}
674	if (ch == '\0')
675		ch = ' ';
676	gc.attr ^= GRID_ATTR_REVERSE;
677	screen_write_putc(&ctx, &gc, ch);
678
679	screen_write_stop(&ctx);
680
681	if (grid_compare(c->status.grid, old_status.grid) == 0) {
682		screen_free(&old_status);
683		return (0);
684	}
685	screen_free(&old_status);
686	return (1);
687}
688
689/* Handle keys in prompt. */
690void
691status_prompt_key(struct client *c, int key)
692{
693	struct paste_buffer	*pb;
694	char   			*s, *first, *last, word[64];
695	size_t			 size, n, off, idx;
696
697	size = strlen(c->prompt_buffer);
698	switch (mode_key_lookup(&c->prompt_mdata, key)) {
699	case MODEKEYCMD_LEFT:
700		if (c->prompt_index > 0) {
701			c->prompt_index--;
702			c->flags |= CLIENT_STATUS;
703		}
704		break;
705	case MODEKEYCMD_RIGHT:
706		if (c->prompt_index < size) {
707			c->prompt_index++;
708			c->flags |= CLIENT_STATUS;
709		}
710		break;
711	case MODEKEYCMD_STARTOFLINE:
712	case MODEKEYCMD_BACKTOINDENTATION:
713		if (c->prompt_index != 0) {
714			c->prompt_index = 0;
715			c->flags |= CLIENT_STATUS;
716		}
717		break;
718	case MODEKEYCMD_ENDOFLINE:
719		if (c->prompt_index != size) {
720			c->prompt_index = size;
721			c->flags |= CLIENT_STATUS;
722		}
723		break;
724	case MODEKEYCMD_COMPLETE:
725		if (*c->prompt_buffer == '\0')
726			break;
727
728		idx = c->prompt_index;
729		if (idx != 0)
730			idx--;
731
732		/* Find the word we are in. */
733		first = c->prompt_buffer + idx;
734		while (first > c->prompt_buffer && *first != ' ')
735			first--;
736		while (*first == ' ')
737			first++;
738		last = c->prompt_buffer + idx;
739		while (*last != '\0' && *last != ' ')
740			last++;
741		while (*last == ' ')
742			last--;
743		if (*last != '\0')
744			last++;
745		if (last <= first ||
746		    ((size_t) (last - first)) > (sizeof word) - 1)
747			break;
748		memcpy(word, first, last - first);
749		word[last - first] = '\0';
750
751		/* And try to complete it. */
752		if ((s = status_prompt_complete(word)) == NULL)
753			break;
754
755		/* Trim out word. */
756		n = size - (last - c->prompt_buffer) + 1; /* with \0 */
757		memmove(first, last, n);
758		size -= last - first;
759
760		/* Insert the new word. */
761 		size += strlen(s);
762		off = first - c->prompt_buffer;
763 		c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 1);
764		first = c->prompt_buffer + off;
765 		memmove(first + strlen(s), first, n);
766 		memcpy(first, s, strlen(s));
767
768		c->prompt_index = (first - c->prompt_buffer) + strlen(s);
769
770		c->flags |= CLIENT_STATUS;
771		break;
772	case MODEKEYCMD_BACKSPACE:
773		if (c->prompt_index != 0) {
774			if (c->prompt_index == size)
775				c->prompt_buffer[--c->prompt_index] = '\0';
776			else {
777				memmove(c->prompt_buffer + c->prompt_index - 1,
778				    c->prompt_buffer + c->prompt_index,
779				    size + 1 - c->prompt_index);
780				c->prompt_index--;
781			}
782			c->flags |= CLIENT_STATUS;
783		}
784		break;
785 	case MODEKEYCMD_DELETE:
786		if (c->prompt_index != size) {
787			memmove(c->prompt_buffer + c->prompt_index,
788			    c->prompt_buffer + c->prompt_index + 1,
789			    size + 1 - c->prompt_index);
790			c->flags |= CLIENT_STATUS;
791		}
792		break;
793	case MODEKEYCMD_UP:
794		if (server_locked)
795			break;
796
797		if (ARRAY_LENGTH(&c->prompt_hdata) == 0)
798			break;
799		if (c->prompt_flags & PROMPT_HIDDEN)
800			memset(c->prompt_buffer, 0, strlen(c->prompt_buffer));
801	       	xfree(c->prompt_buffer);
802
803		c->prompt_buffer = xstrdup(ARRAY_ITEM(&c->prompt_hdata,
804		    ARRAY_LENGTH(&c->prompt_hdata) - 1 - c->prompt_hindex));
805		if (c->prompt_hindex != ARRAY_LENGTH(&c->prompt_hdata) - 1)
806			c->prompt_hindex++;
807
808		c->prompt_index = strlen(c->prompt_buffer);
809		c->flags |= CLIENT_STATUS;
810		break;
811	case MODEKEYCMD_DOWN:
812		if (server_locked)
813			break;
814
815		if (c->prompt_flags & PROMPT_HIDDEN)
816			memset(c->prompt_buffer, 0, strlen(c->prompt_buffer));
817		xfree(c->prompt_buffer);
818
819		if (c->prompt_hindex != 0) {
820			c->prompt_hindex--;
821			c->prompt_buffer = xstrdup(ARRAY_ITEM(
822			    &c->prompt_hdata, ARRAY_LENGTH(
823			    &c->prompt_hdata) - 1 - c->prompt_hindex));
824		} else
825			c->prompt_buffer = xstrdup("");
826
827		c->prompt_index = strlen(c->prompt_buffer);
828		c->flags |= CLIENT_STATUS;
829		break;
830	case MODEKEYCMD_PASTE:
831		if ((pb = paste_get_top(&c->session->buffers)) == NULL)
832			break;
833		if ((last = strchr(pb->data, '\n')) == NULL)
834			last = strchr(pb->data, '\0');
835		n = last - pb->data;
836
837		c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + n + 1);
838		if (c->prompt_index == size) {
839			memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
840			c->prompt_index += n;
841			c->prompt_buffer[c->prompt_index] = '\0';
842		} else {
843			memmove(c->prompt_buffer + c->prompt_index + n,
844			    c->prompt_buffer + c->prompt_index,
845			    size + 1 - c->prompt_index);
846			memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
847			c->prompt_index += n;
848		}
849
850		c->flags |= CLIENT_STATUS;
851		break;
852 	case MODEKEYCMD_CHOOSE:
853		if (*c->prompt_buffer != '\0') {
854			status_prompt_add_history(c);
855			if (c->prompt_callback(
856			    c->prompt_data, c->prompt_buffer) == 0)
857				status_prompt_clear(c);
858			break;
859		}
860		/* FALLTHROUGH */
861	case MODEKEYCMD_QUIT:
862		if (c->prompt_callback(c->prompt_data, NULL) == 0)
863			status_prompt_clear(c);
864		break;
865	case MODEKEYCMD_OTHERKEY:
866		if (key < 32 || key > 126)
867			break;
868		c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 2);
869
870		if (c->prompt_index == size) {
871			c->prompt_buffer[c->prompt_index++] = key;
872			c->prompt_buffer[c->prompt_index] = '\0';
873		} else {
874			memmove(c->prompt_buffer + c->prompt_index + 1,
875			    c->prompt_buffer + c->prompt_index,
876			    size + 1 - c->prompt_index);
877			c->prompt_buffer[c->prompt_index++] = key;
878		}
879
880		if (c->prompt_flags & PROMPT_SINGLE) {
881			if (c->prompt_callback(
882			    c->prompt_data, c->prompt_buffer) == 0)
883				status_prompt_clear(c);
884		}
885
886		c->flags |= CLIENT_STATUS;
887		break;
888	default:
889		break;
890	}
891}
892
893/* Add line to the history. */
894void
895status_prompt_add_history(struct client *c)
896{
897	if (server_locked)
898		return;
899
900	if (ARRAY_LENGTH(&c->prompt_hdata) > 0 &&
901	    strcmp(ARRAY_LAST(&c->prompt_hdata), c->prompt_buffer) == 0)
902		return;
903
904	if (ARRAY_LENGTH(&c->prompt_hdata) == PROMPT_HISTORY) {
905		xfree(ARRAY_FIRST(&c->prompt_hdata));
906		ARRAY_REMOVE(&c->prompt_hdata, 0);
907	}
908
909	ARRAY_ADD(&c->prompt_hdata, xstrdup(c->prompt_buffer));
910}
911
912/* Complete word. */
913char *
914status_prompt_complete(const char *s)
915{
916	const struct cmd_entry 	      **cmdent;
917	const struct set_option_entry  *optent;
918	ARRAY_DECL(, const char *)	list;
919	char			       *prefix, *s2;
920	u_int			 	i;
921	size_t			 	j;
922
923	if (*s == '\0')
924		return (NULL);
925
926	/* First, build a list of all the possible matches. */
927	ARRAY_INIT(&list);
928	for (cmdent = cmd_table; *cmdent != NULL; cmdent++) {
929		if (strncmp((*cmdent)->name, s, strlen(s)) == 0)
930			ARRAY_ADD(&list, (*cmdent)->name);
931	}
932	for (i = 0; i < NSETOPTION; i++) {
933		optent = &set_option_table[i];
934		if (strncmp(optent->name, s, strlen(s)) == 0)
935			ARRAY_ADD(&list, optent->name);
936	}
937	for (i = 0; i < NSETWINDOWOPTION; i++) {
938		optent = &set_window_option_table[i];
939		if (strncmp(optent->name, s, strlen(s)) == 0)
940			ARRAY_ADD(&list, optent->name);
941	}
942
943	/* If none, bail now. */
944	if (ARRAY_LENGTH(&list) == 0) {
945		ARRAY_FREE(&list);
946		return (NULL);
947	}
948
949	/* If an exact match, return it, with a trailing space. */
950	if (ARRAY_LENGTH(&list) == 1) {
951		xasprintf(&s2, "%s ", ARRAY_FIRST(&list));
952		ARRAY_FREE(&list);
953		return (s2);
954	}
955
956	/* Now loop through the list and find the longest common prefix. */
957	prefix = xstrdup(ARRAY_FIRST(&list));
958	for (i = 1; i < ARRAY_LENGTH(&list); i++) {
959		s = ARRAY_ITEM(&list, i);
960
961		j = strlen(s);
962		if (j > strlen(prefix))
963			j = strlen(prefix);
964		for (; j > 0; j--) {
965			if (prefix[j - 1] != s[j - 1])
966				prefix[j - 1] = '\0';
967		}
968	}
969
970	ARRAY_FREE(&list);
971	return (prefix);
972}
973