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