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