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