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