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