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