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