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