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