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