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