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