status.c revision 1.65
1/* $OpenBSD: status.c,v 1.65 2010/12/11 16:05:57 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		tmp[0] = ' ';
397		if (wl->flags & WINLINK_CONTENT)
398			tmp[0] = '+';
399		else if (wl->flags & WINLINK_BELL)
400			tmp[0] = '!';
401		else if (wl->flags & WINLINK_ACTIVITY)
402			tmp[0] = '#';
403		else if (wl->flags & WINLINK_SILENCE)
404			tmp[0] = '~';
405		else if (wl == s->curw)
406			tmp[0] = '*';
407		else if (wl == TAILQ_FIRST(&s->lastw))
408			tmp[0] = '-';
409		tmp[1] = '\0';
410		ptr = tmp;
411		goto do_replace;
412	case '[':
413		/*
414		 * Embedded style, handled at display time. Leave present and
415		 * skip input until ].
416		 */
417		ch = ']';
418		goto skip_to;
419	case '#':
420		*(*optr)++ = '#';
421		break;
422	}
423
424	return;
425
426do_replace:
427	ptrlen = strlen(ptr);
428	if ((size_t) limit < ptrlen)
429		ptrlen = limit;
430
431	if (*optr + ptrlen >= out + outsize - 1)
432		return;
433	while (ptrlen > 0 && *ptr != '\0') {
434		*(*optr)++ = *ptr++;
435		ptrlen--;
436	}
437
438	if (freeptr != NULL)
439		xfree(freeptr);
440	return;
441
442skip_to:
443	*(*optr)++ = '#';
444
445	(*iptr)--;	/* include ch */
446	while (**iptr != ch && **iptr != '\0') {
447		if (*optr >=  out + outsize - 1)
448			break;
449		*(*optr)++ = *(*iptr)++;
450	}
451}
452
453/* Replace special sequences in fmt. */
454char *
455status_replace(struct client *c,
456    struct winlink *wl, const char *fmt, time_t t, int jobsflag)
457{
458	static char	out[BUFSIZ];
459	char		in[BUFSIZ], ch, *iptr, *optr;
460
461	strftime(in, sizeof in, fmt, localtime(&t));
462	in[(sizeof in) - 1] = '\0';
463
464	iptr = in;
465	optr = out;
466
467	while (*iptr != '\0') {
468		if (optr >= out + (sizeof out) - 1)
469			break;
470		ch = *iptr++;
471
472		if (ch != '#') {
473			*optr++ = ch;
474			continue;
475		}
476		status_replace1(c, wl, &iptr, &optr, out, sizeof out, jobsflag);
477	}
478	*optr = '\0';
479
480	return (xstrdup(out));
481}
482
483/* Figure out job name and get its result, starting it off if necessary. */
484char *
485status_job(struct client *c, char **iptr)
486{
487	struct job	*job;
488	char   		*cmd;
489	int		 lastesc;
490	size_t		 len;
491
492	if (**iptr == '\0')
493		return (NULL);
494	if (**iptr == ')') {		/* no command given */
495		(*iptr)++;
496		return (NULL);
497	}
498
499	cmd = xmalloc(strlen(*iptr) + 1);
500	len = 0;
501
502	lastesc = 0;
503	for (; **iptr != '\0'; (*iptr)++) {
504		if (!lastesc && **iptr == ')')
505			break;		/* unescaped ) is the end */
506		if (!lastesc && **iptr == '\\') {
507			lastesc = 1;
508			continue;	/* skip \ if not escaped */
509		}
510		lastesc = 0;
511		cmd[len++] = **iptr;
512	}
513	if (**iptr == '\0')		/* no terminating ) */ {
514		xfree(cmd);
515		return (NULL);
516	}
517	(*iptr)++;			/* skip final ) */
518	cmd[len] = '\0';
519
520	job = job_get(&c->status_jobs, cmd);
521	if (job == NULL) {
522		job = job_add(&c->status_jobs,
523		    JOB_PERSIST, c, cmd, status_job_callback, xfree, NULL);
524		job_run(job);
525	}
526	xfree(cmd);
527	if (job->data == NULL)
528		return (xstrdup(""));
529	return (xstrdup(job->data));
530}
531
532/* Job has finished: save its result. */
533void
534status_job_callback(struct job *job)
535{
536	char	*line, *buf;
537	size_t	 len;
538
539	buf = NULL;
540	if ((line = evbuffer_readline(job->event->input)) == NULL) {
541		len = EVBUFFER_LENGTH(job->event->input);
542		buf = xmalloc(len + 1);
543		if (len != 0)
544			memcpy(buf, EVBUFFER_DATA(job->event->input), len);
545		buf[len] = '\0';
546	}
547
548	if (job->data != NULL)
549		xfree(job->data);
550	else
551		server_redraw_client(job->client);
552
553	if (line == NULL)
554		job->data = buf;
555	else
556		job->data = xstrdup(line);
557}
558
559/* Return winlink status line entry and adjust gc as necessary. */
560char *
561status_print(
562    struct client *c, struct winlink *wl, time_t t, struct grid_cell *gc)
563{
564	struct options	*oo = &wl->window->options;
565	struct session	*s = c->session;
566	const char	*fmt;
567	char   		*text;
568	u_char		 fg, bg, attr;
569
570	fg = options_get_number(oo, "window-status-fg");
571	if (fg != 8)
572		colour_set_fg(gc, fg);
573	bg = options_get_number(oo, "window-status-bg");
574	if (bg != 8)
575		colour_set_bg(gc, bg);
576	attr = options_get_number(oo, "window-status-attr");
577	if (attr != 0)
578		gc->attr = attr;
579	fmt = options_get_string(oo, "window-status-format");
580	if (wl == s->curw) {
581		fg = options_get_number(oo, "window-status-current-fg");
582		if (fg != 8)
583			colour_set_fg(gc, fg);
584		bg = options_get_number(oo, "window-status-current-bg");
585		if (bg != 8)
586			colour_set_bg(gc, bg);
587		attr = options_get_number(oo, "window-status-current-attr");
588		if (attr != 0)
589			gc->attr = attr;
590		fmt = options_get_string(oo, "window-status-current-format");
591	}
592
593	if (wl->flags & WINLINK_ALERTFLAGS) {
594		fg = options_get_number(oo, "window-status-alert-fg");
595		if (fg != 8)
596			colour_set_fg(gc, fg);
597		bg = options_get_number(oo, "window-status-alert-bg");
598		if (bg != 8)
599			colour_set_bg(gc, bg);
600		attr = options_get_number(oo, "window-status-alert-attr");
601		if (attr != 0)
602			gc->attr = attr;
603	}
604
605	text = status_replace(c, wl, fmt, t, 1);
606	return (text);
607}
608
609/* Set a status line message. */
610void printflike2
611status_message_set(struct client *c, const char *fmt, ...)
612{
613	struct timeval		 tv;
614	struct session		*s = c->session;
615	struct message_entry	*msg;
616	va_list			 ap;
617	int			 delay;
618	u_int			 i, limit;
619
620	status_prompt_clear(c);
621	status_message_clear(c);
622
623	va_start(ap, fmt);
624	xvasprintf(&c->message_string, fmt, ap);
625	va_end(ap);
626
627	ARRAY_EXPAND(&c->message_log, 1);
628	msg = &ARRAY_LAST(&c->message_log);
629	msg->msg_time = time(NULL);
630	msg->msg = xstrdup(c->message_string);
631
632	if (s == NULL)
633		limit = 0;
634	else
635		limit = options_get_number(&s->options, "message-limit");
636	if (ARRAY_LENGTH(&c->message_log) > limit) {
637		limit = ARRAY_LENGTH(&c->message_log) - limit;
638		for (i = 0; i < limit; i++) {
639			msg = &ARRAY_FIRST(&c->message_log);
640			xfree(msg->msg);
641			ARRAY_REMOVE(&c->message_log, 0);
642		}
643	}
644
645	delay = options_get_number(&c->session->options, "display-time");
646	tv.tv_sec = delay / 1000;
647	tv.tv_usec = (delay % 1000) * 1000L;
648
649	evtimer_del(&c->message_timer);
650	evtimer_set(&c->message_timer, status_message_callback, c);
651	evtimer_add(&c->message_timer, &tv);
652
653	c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
654	c->flags |= CLIENT_STATUS;
655}
656
657/* Clear status line message. */
658void
659status_message_clear(struct client *c)
660{
661	if (c->message_string == NULL)
662		return;
663
664	xfree(c->message_string);
665	c->message_string = NULL;
666
667	c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
668	c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
669
670	screen_reinit(&c->status);
671}
672
673/* Clear status line message after timer expires. */
674/* ARGSUSED */
675void
676status_message_callback(unused int fd, unused short event, void *data)
677{
678	struct client	*c = data;
679
680	status_message_clear(c);
681}
682
683/* Draw client message on status line of present else on last line. */
684int
685status_message_redraw(struct client *c)
686{
687	struct screen_write_ctx		ctx;
688	struct session		       *s = c->session;
689	struct screen		        old_status;
690	size_t			        len;
691	struct grid_cell		gc;
692	int				utf8flag;
693
694	if (c->tty.sx == 0 || c->tty.sy == 0)
695		return (0);
696	memcpy(&old_status, &c->status, sizeof old_status);
697	screen_init(&c->status, c->tty.sx, 1, 0);
698
699	utf8flag = options_get_number(&s->options, "status-utf8");
700
701	len = screen_write_strlen(utf8flag, "%s", c->message_string);
702	if (len > c->tty.sx)
703		len = c->tty.sx;
704
705	memcpy(&gc, &grid_default_cell, sizeof gc);
706	colour_set_fg(&gc, options_get_number(&s->options, "message-fg"));
707	colour_set_bg(&gc, options_get_number(&s->options, "message-bg"));
708	gc.attr |= options_get_number(&s->options, "message-attr");
709
710	screen_write_start(&ctx, NULL, &c->status);
711
712	screen_write_cursormove(&ctx, 0, 0);
713	screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->message_string);
714	for (; len < c->tty.sx; len++)
715		screen_write_putc(&ctx, &gc, ' ');
716
717	screen_write_stop(&ctx);
718
719	if (grid_compare(c->status.grid, old_status.grid) == 0) {
720		screen_free(&old_status);
721		return (0);
722	}
723	screen_free(&old_status);
724	return (1);
725}
726
727/* Enable status line prompt. */
728void
729status_prompt_set(struct client *c, const char *msg,
730    int (*callbackfn)(void *, const char *), void (*freefn)(void *),
731    void *data, int flags)
732{
733	int	keys;
734
735	status_message_clear(c);
736	status_prompt_clear(c);
737
738	c->prompt_string = xstrdup(msg);
739
740	c->prompt_buffer = xstrdup("");
741	c->prompt_index = 0;
742
743	c->prompt_callbackfn = callbackfn;
744	c->prompt_freefn = freefn;
745	c->prompt_data = data;
746
747	c->prompt_hindex = 0;
748
749	c->prompt_flags = flags;
750
751	keys = options_get_number(&c->session->options, "status-keys");
752	if (keys == MODEKEY_EMACS)
753		mode_key_init(&c->prompt_mdata, &mode_key_tree_emacs_edit);
754	else
755		mode_key_init(&c->prompt_mdata, &mode_key_tree_vi_edit);
756
757	c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
758	c->flags |= CLIENT_STATUS;
759}
760
761/* Remove status line prompt. */
762void
763status_prompt_clear(struct client *c)
764{
765	if (c->prompt_string == NULL)
766		return;
767
768	if (c->prompt_freefn != NULL && c->prompt_data != NULL)
769		c->prompt_freefn(c->prompt_data);
770
771	xfree(c->prompt_string);
772	c->prompt_string = NULL;
773
774	xfree(c->prompt_buffer);
775	c->prompt_buffer = NULL;
776
777	c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
778	c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
779
780	screen_reinit(&c->status);
781}
782
783/* Update status line prompt with a new prompt string. */
784void
785status_prompt_update(struct client *c, const char *msg)
786{
787	xfree(c->prompt_string);
788	c->prompt_string = xstrdup(msg);
789
790	*c->prompt_buffer = '\0';
791	c->prompt_index = 0;
792
793	c->prompt_hindex = 0;
794
795	c->flags |= CLIENT_STATUS;
796}
797
798/* Draw client prompt on status line of present else on last line. */
799int
800status_prompt_redraw(struct client *c)
801{
802	struct screen_write_ctx		ctx;
803	struct session		       *s = c->session;
804	struct screen		        old_status;
805	size_t			        i, size, left, len, off;
806	struct grid_cell		gc, *gcp;
807	int				utf8flag;
808
809	if (c->tty.sx == 0 || c->tty.sy == 0)
810		return (0);
811	memcpy(&old_status, &c->status, sizeof old_status);
812	screen_init(&c->status, c->tty.sx, 1, 0);
813
814	utf8flag = options_get_number(&s->options, "status-utf8");
815
816	len = screen_write_strlen(utf8flag, "%s", c->prompt_string);
817	if (len > c->tty.sx)
818		len = c->tty.sx;
819	off = 0;
820
821	memcpy(&gc, &grid_default_cell, sizeof gc);
822	colour_set_fg(&gc, options_get_number(&s->options, "message-fg"));
823	colour_set_bg(&gc, options_get_number(&s->options, "message-bg"));
824	gc.attr |= options_get_number(&s->options, "message-attr");
825
826	screen_write_start(&ctx, NULL, &c->status);
827
828	screen_write_cursormove(&ctx, 0, 0);
829	screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->prompt_string);
830
831	left = c->tty.sx - len;
832	if (left != 0) {
833		size = screen_write_strlen(utf8flag, "%s", c->prompt_buffer);
834		if (c->prompt_index >= left) {
835			off = c->prompt_index - left + 1;
836			if (c->prompt_index == size)
837				left--;
838			size = left;
839		}
840		screen_write_nputs(
841		    &ctx, left, &gc, utf8flag, "%s", c->prompt_buffer + off);
842
843		for (i = len + size; i < c->tty.sx; i++)
844			screen_write_putc(&ctx, &gc, ' ');
845	}
846
847	screen_write_stop(&ctx);
848
849	/* Apply fake cursor. */
850	off = len + c->prompt_index - off;
851	gcp = grid_view_get_cell(c->status.grid, off, 0);
852	gcp->attr ^= GRID_ATTR_REVERSE;
853
854	if (grid_compare(c->status.grid, old_status.grid) == 0) {
855		screen_free(&old_status);
856		return (0);
857	}
858	screen_free(&old_status);
859	return (1);
860}
861
862/* Handle keys in prompt. */
863void
864status_prompt_key(struct client *c, int key)
865{
866	struct paste_buffer	*pb;
867	char   			*s, *first, *last, word[64], swapc;
868	u_char			 ch;
869	size_t			 size, n, off, idx;
870
871	size = strlen(c->prompt_buffer);
872	switch (mode_key_lookup(&c->prompt_mdata, key)) {
873	case MODEKEYEDIT_CURSORLEFT:
874		if (c->prompt_index > 0) {
875			c->prompt_index--;
876			c->flags |= CLIENT_STATUS;
877		}
878		break;
879	case MODEKEYEDIT_SWITCHMODEAPPEND:
880	case MODEKEYEDIT_CURSORRIGHT:
881		if (c->prompt_index < size) {
882			c->prompt_index++;
883			c->flags |= CLIENT_STATUS;
884		}
885		break;
886	case MODEKEYEDIT_STARTOFLINE:
887		if (c->prompt_index != 0) {
888			c->prompt_index = 0;
889			c->flags |= CLIENT_STATUS;
890		}
891		break;
892	case MODEKEYEDIT_ENDOFLINE:
893		if (c->prompt_index != size) {
894			c->prompt_index = size;
895			c->flags |= CLIENT_STATUS;
896		}
897		break;
898	case MODEKEYEDIT_COMPLETE:
899		if (*c->prompt_buffer == '\0')
900			break;
901
902		idx = c->prompt_index;
903		if (idx != 0)
904			idx--;
905
906		/* Find the word we are in. */
907		first = c->prompt_buffer + idx;
908		while (first > c->prompt_buffer && *first != ' ')
909			first--;
910		while (*first == ' ')
911			first++;
912		last = c->prompt_buffer + idx;
913		while (*last != '\0' && *last != ' ')
914			last++;
915		while (*last == ' ')
916			last--;
917		if (*last != '\0')
918			last++;
919		if (last <= first ||
920		    ((size_t) (last - first)) > (sizeof word) - 1)
921			break;
922		memcpy(word, first, last - first);
923		word[last - first] = '\0';
924
925		/* And try to complete it. */
926		if ((s = status_prompt_complete(word)) == NULL)
927			break;
928
929		/* Trim out word. */
930		n = size - (last - c->prompt_buffer) + 1; /* with \0 */
931		memmove(first, last, n);
932		size -= last - first;
933
934		/* Insert the new word. */
935		size += strlen(s);
936		off = first - c->prompt_buffer;
937		c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 1);
938		first = c->prompt_buffer + off;
939		memmove(first + strlen(s), first, n);
940		memcpy(first, s, strlen(s));
941
942		c->prompt_index = (first - c->prompt_buffer) + strlen(s);
943		xfree(s);
944
945		c->flags |= CLIENT_STATUS;
946		break;
947	case MODEKEYEDIT_BACKSPACE:
948		if (c->prompt_index != 0) {
949			if (c->prompt_index == size)
950				c->prompt_buffer[--c->prompt_index] = '\0';
951			else {
952				memmove(c->prompt_buffer + c->prompt_index - 1,
953				    c->prompt_buffer + c->prompt_index,
954				    size + 1 - c->prompt_index);
955				c->prompt_index--;
956			}
957			c->flags |= CLIENT_STATUS;
958		}
959		break;
960	case MODEKEYEDIT_DELETE:
961		if (c->prompt_index != size) {
962			memmove(c->prompt_buffer + c->prompt_index,
963			    c->prompt_buffer + c->prompt_index + 1,
964			    size + 1 - c->prompt_index);
965			c->flags |= CLIENT_STATUS;
966		}
967		break;
968	case MODEKEYEDIT_DELETELINE:
969		*c->prompt_buffer = '\0';
970		c->prompt_index = 0;
971		c->flags |= CLIENT_STATUS;
972		break;
973	case MODEKEYEDIT_DELETETOENDOFLINE:
974		if (c->prompt_index < size) {
975			c->prompt_buffer[c->prompt_index] = '\0';
976			c->flags |= CLIENT_STATUS;
977		}
978		break;
979	case MODEKEYEDIT_HISTORYUP:
980		s = status_prompt_up_history(&c->prompt_hindex);
981		if (s == NULL)
982			break;
983	       	xfree(c->prompt_buffer);
984		c->prompt_buffer = xstrdup(s);
985		c->prompt_index = strlen(c->prompt_buffer);
986		c->flags |= CLIENT_STATUS;
987		break;
988	case MODEKEYEDIT_HISTORYDOWN:
989		s = status_prompt_down_history(&c->prompt_hindex);
990		if (s == NULL)
991			break;
992		xfree(c->prompt_buffer);
993		c->prompt_buffer = xstrdup(s);
994		c->prompt_index = strlen(c->prompt_buffer);
995		c->flags |= CLIENT_STATUS;
996		break;
997	case MODEKEYEDIT_PASTE:
998		if ((pb = paste_get_top(&c->session->buffers)) == NULL)
999			break;
1000		for (n = 0; n < pb->size; n++) {
1001			ch = (u_char) pb->data[n];
1002			if (ch < 32 || ch == 127)
1003				break;
1004		}
1005
1006		c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + n + 1);
1007		if (c->prompt_index == size) {
1008			memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
1009			c->prompt_index += n;
1010			c->prompt_buffer[c->prompt_index] = '\0';
1011		} else {
1012			memmove(c->prompt_buffer + c->prompt_index + n,
1013			    c->prompt_buffer + c->prompt_index,
1014			    size + 1 - c->prompt_index);
1015			memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
1016			c->prompt_index += n;
1017		}
1018
1019		c->flags |= CLIENT_STATUS;
1020		break;
1021	case MODEKEYEDIT_TRANSPOSECHARS:
1022		idx = c->prompt_index;
1023		if (idx < size)
1024			idx++;
1025		if (idx >= 2) {
1026			swapc = c->prompt_buffer[idx - 2];
1027			c->prompt_buffer[idx - 2] = c->prompt_buffer[idx - 1];
1028			c->prompt_buffer[idx - 1] = swapc;
1029			c->prompt_index = idx;
1030			c->flags |= CLIENT_STATUS;
1031		}
1032		break;
1033	case MODEKEYEDIT_ENTER:
1034		if (*c->prompt_buffer != '\0')
1035			status_prompt_add_history(c->prompt_buffer);
1036		if (c->prompt_callbackfn(c->prompt_data, c->prompt_buffer) == 0)
1037			status_prompt_clear(c);
1038		break;
1039	case MODEKEYEDIT_CANCEL:
1040		if (c->prompt_callbackfn(c->prompt_data, NULL) == 0)
1041			status_prompt_clear(c);
1042		break;
1043	case MODEKEY_OTHER:
1044		if ((key & 0xff00) != 0 || key < 32 || key == 127)
1045			break;
1046		c->prompt_buffer = xrealloc(c->prompt_buffer, 1, size + 2);
1047
1048		if (c->prompt_index == size) {
1049			c->prompt_buffer[c->prompt_index++] = key;
1050			c->prompt_buffer[c->prompt_index] = '\0';
1051		} else {
1052			memmove(c->prompt_buffer + c->prompt_index + 1,
1053			    c->prompt_buffer + c->prompt_index,
1054			    size + 1 - c->prompt_index);
1055			c->prompt_buffer[c->prompt_index++] = key;
1056		}
1057
1058		if (c->prompt_flags & PROMPT_SINGLE) {
1059			if (c->prompt_callbackfn(
1060			    c->prompt_data, c->prompt_buffer) == 0)
1061				status_prompt_clear(c);
1062		}
1063
1064		c->flags |= CLIENT_STATUS;
1065		break;
1066	default:
1067		break;
1068	}
1069}
1070
1071/* Get previous line from the history. */
1072const char *
1073status_prompt_up_history(u_int *idx)
1074{
1075	u_int size;
1076
1077	/*
1078	 * History runs from 0 to size - 1.
1079	 *
1080	 * Index is from 0 to size. Zero is empty.
1081	 */
1082
1083	size = ARRAY_LENGTH(&status_prompt_history);
1084	if (size == 0 || *idx == size)
1085		return (NULL);
1086	(*idx)++;
1087	return (ARRAY_ITEM(&status_prompt_history, size - *idx));
1088}
1089
1090/* Get next line from the history. */
1091const char *
1092status_prompt_down_history(u_int *idx)
1093{
1094	u_int size;
1095
1096	size = ARRAY_LENGTH(&status_prompt_history);
1097	if (size == 0 || *idx == 0)
1098		return ("");
1099	(*idx)--;
1100	if (*idx == 0)
1101		return ("");
1102	return (ARRAY_ITEM(&status_prompt_history, size - *idx));
1103}
1104
1105/* Add line to the history. */
1106void
1107status_prompt_add_history(const char *line)
1108{
1109	u_int size;
1110
1111	size = ARRAY_LENGTH(&status_prompt_history);
1112	if (size > 0 && strcmp(ARRAY_LAST(&status_prompt_history), line) == 0)
1113		return;
1114
1115	if (size == PROMPT_HISTORY) {
1116		xfree(ARRAY_FIRST(&status_prompt_history));
1117		ARRAY_REMOVE(&status_prompt_history, 0);
1118	}
1119
1120	ARRAY_ADD(&status_prompt_history, xstrdup(line));
1121}
1122
1123/* Complete word. */
1124char *
1125status_prompt_complete(const char *s)
1126{
1127	const struct cmd_entry 	      **cmdent;
1128	const struct set_option_entry  *entry;
1129	ARRAY_DECL(, const char *)	list;
1130	char			       *prefix, *s2;
1131	u_int				i;
1132	size_t			 	j;
1133
1134	if (*s == '\0')
1135		return (NULL);
1136
1137	/* First, build a list of all the possible matches. */
1138	ARRAY_INIT(&list);
1139	for (cmdent = cmd_table; *cmdent != NULL; cmdent++) {
1140		if (strncmp((*cmdent)->name, s, strlen(s)) == 0)
1141			ARRAY_ADD(&list, (*cmdent)->name);
1142	}
1143	for (entry = set_option_table; entry->name != NULL; entry++) {
1144		if (strncmp(entry->name, s, strlen(s)) == 0)
1145			ARRAY_ADD(&list, entry->name);
1146	}
1147	for (entry = set_session_option_table; entry->name != NULL; entry++) {
1148		if (strncmp(entry->name, s, strlen(s)) == 0)
1149			ARRAY_ADD(&list, entry->name);
1150	}
1151	for (entry = set_window_option_table; entry->name != NULL; entry++) {
1152		if (strncmp(entry->name, s, strlen(s)) == 0)
1153			ARRAY_ADD(&list, entry->name);
1154	}
1155
1156	/* If none, bail now. */
1157	if (ARRAY_LENGTH(&list) == 0) {
1158		ARRAY_FREE(&list);
1159		return (NULL);
1160	}
1161
1162	/* If an exact match, return it, with a trailing space. */
1163	if (ARRAY_LENGTH(&list) == 1) {
1164		xasprintf(&s2, "%s ", ARRAY_FIRST(&list));
1165		ARRAY_FREE(&list);
1166		return (s2);
1167	}
1168
1169	/* Now loop through the list and find the longest common prefix. */
1170	prefix = xstrdup(ARRAY_FIRST(&list));
1171	for (i = 1; i < ARRAY_LENGTH(&list); i++) {
1172		s = ARRAY_ITEM(&list, i);
1173
1174		j = strlen(s);
1175		if (j > strlen(prefix))
1176			j = strlen(prefix);
1177		for (; j > 0; j--) {
1178			if (prefix[j - 1] != s[j - 1])
1179				prefix[j - 1] = '\0';
1180		}
1181	}
1182
1183	ARRAY_FREE(&list);
1184	return (prefix);
1185}
1186