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