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