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