xref: /minix/external/bsd/tmux/dist/status.c (revision e3b78ef1)
1 /* $Id: status.c,v 1.1.1.2 2011/08/17 18:40:05 jmmv 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 
32 char   *status_redraw_get_left(
33 	    struct client *, time_t, int, struct grid_cell *, size_t *);
34 char   *status_redraw_get_right(
35 	    struct client *, time_t, int, struct grid_cell *, size_t *);
36 char   *status_find_job(struct client *, char **);
37 void	status_job_free(void *);
38 void	status_job_callback(struct job *);
39 char   *status_print(
40 	    struct client *, struct winlink *, time_t, struct grid_cell *);
41 void	status_replace1(struct client *, struct session *, struct winlink *,
42 	    struct window_pane *, char **, char **, char *, size_t, int);
43 void	status_message_callback(int, short, void *);
44 
45 const char *status_prompt_up_history(u_int *);
46 const char *status_prompt_down_history(u_int *);
47 void	status_prompt_add_history(const char *);
48 char   *status_prompt_complete(const char *);
49 
50 /* Status prompt history. */
51 ARRAY_DECL(, char *) status_prompt_history = ARRAY_INITIALIZER;
52 
53 /* Status output tree. */
54 RB_GENERATE(status_out_tree, status_out, entry, status_out_cmp);
55 
56 /* Output tree comparison function. */
57 int
58 status_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. */
64 char *
65 status_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. */
94 char *
95 status_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. */
124 void
125 status_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. */
141 int
142 status_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 
295 draw:
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 
352 out:
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 #). */
367 void
368 status_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 
456 do_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 
472 skip_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. */
484 char *
485 status_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. */
515 char *
516 status_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. */
578 void
579 status_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. */
597 void
598 status_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. */
609 void
610 status_job_free(void *data)
611 {
612 	struct client	*c = data;
613 
614 	c->references--;
615 }
616 
617 /* Job has finished: save its result. */
618 void
619 status_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. */
649 char *
650 status_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. */
699 void printflike2
700 status_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. */
747 void
748 status_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 */
764 void
765 status_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. */
773 int
774 status_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. */
817 void
818 status_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. */
855 void
856 status_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. */
877 void
878 status_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. */
897 int
898 status_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. */
961 void
962 status_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. */
1171 const char *
1172 status_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. */
1190 const char *
1191 status_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. */
1205 void
1206 status_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. */
1223 char *
1224 status_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