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