status.c revision 1.130
1/* $OpenBSD: status.c,v 1.130 2015/07/20 15:50:04 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(struct client *, time_t, int, struct grid_cell *,
33	    size_t *);
34char   *status_redraw_get_right(struct client *, time_t, int,
35	    struct grid_cell *, size_t *);
36char   *status_print(struct client *, struct winlink *, time_t,
37	    struct grid_cell *);
38char   *status_replace(struct client *, struct winlink *, const char *, time_t);
39void	status_replace1(char **, char **, char *, size_t);
40void	status_message_callback(int, short, void *);
41
42const char *status_prompt_up_history(u_int *);
43const char *status_prompt_down_history(u_int *);
44void	status_prompt_add_history(const char *);
45
46const char **status_prompt_complete_list(u_int *, const char *);
47char   *status_prompt_complete_prefix(const char **, u_int);
48char   *status_prompt_complete(struct session *, const char *);
49
50char   *status_prompt_find_history_file(void);
51
52/* Status prompt history. */
53#define PROMPT_HISTORY 100
54char	**status_prompt_hlist;
55u_int	  status_prompt_hsize;
56
57/* Find the history file to load/save from/to. */
58char *
59status_prompt_find_history_file(void)
60{
61	const char	*home, *history_file;
62	char		*path;
63
64	history_file = options_get_string(&global_options, "history-file");
65	if (*history_file == '\0')
66		return (NULL);
67	if (*history_file == '/')
68		return (xstrdup(history_file));
69
70	if (history_file[0] != '~' || history_file[1] != '/')
71		return (NULL);
72	if ((home = find_home()) == NULL)
73		return (NULL);
74	xasprintf(&path, "%s%s", home, history_file + 1);
75	return (path);
76}
77
78/* Load status prompt history from file. */
79void
80status_prompt_load_history(void)
81{
82	FILE	*f;
83	char	*history_file, *line, *tmp;
84	size_t	 length;
85
86	if ((history_file = status_prompt_find_history_file()) == NULL)
87		return;
88	log_debug("loading history from %s", history_file);
89
90	f = fopen(history_file, "r");
91	if (f == NULL) {
92		log_debug("%s: %s", history_file, strerror(errno));
93		free(history_file);
94		return;
95	}
96	free(history_file);
97
98	for (;;) {
99		if ((line = fgetln(f, &length)) == NULL)
100			break;
101
102		if (length > 0) {
103			if (line[length - 1] == '\n') {
104				line[length - 1] = '\0';
105				status_prompt_add_history(line);
106			} else {
107				tmp = xmalloc(length + 1);
108				memcpy(tmp, line, length);
109				tmp[length] = '\0';
110				status_prompt_add_history(tmp);
111				free(tmp);
112			}
113		}
114	}
115	fclose(f);
116}
117
118/* Save status prompt history to file. */
119void
120status_prompt_save_history(void)
121{
122	FILE	*f;
123	u_int	 i;
124	char	*history_file;
125
126	if ((history_file = status_prompt_find_history_file()) == NULL)
127		return;
128	log_debug("saving history to %s", history_file);
129
130	f = fopen(history_file, "w");
131	if (f == NULL) {
132		log_debug("%s: %s", history_file, strerror(errno));
133		free(history_file);
134		return;
135	}
136	free(history_file);
137
138	for (i = 0; i < status_prompt_hsize; i++) {
139		fputs(status_prompt_hlist[i], f);
140		fputc('\n', f);
141	}
142	fclose(f);
143
144}
145
146/* Status output tree. */
147RB_GENERATE(status_out_tree, status_out, entry, status_out_cmp);
148
149/* Output tree comparison function. */
150int
151status_out_cmp(struct status_out *so1, struct status_out *so2)
152{
153	return (strcmp(so1->cmd, so2->cmd));
154}
155
156/* Get screen line of status line. -1 means off. */
157int
158status_at_line(struct client *c)
159{
160	struct session	*s = c->session;
161
162	if (!options_get_number(&s->options, "status"))
163		return (-1);
164
165	if (options_get_number(&s->options, "status-position") == 0)
166		return (0);
167	return (c->tty.sy - 1);
168}
169
170/* Retrieve options for left string. */
171char *
172status_redraw_get_left(struct client *c, time_t t, int utf8flag,
173    struct grid_cell *gc, size_t *size)
174{
175	struct session	*s = c->session;
176	const char	*template;
177	char		*left;
178	size_t		 leftlen;
179
180	style_apply_update(gc, &s->options, "status-left-style");
181
182	template = options_get_string(&s->options, "status-left");
183	left = status_replace(c, NULL, template, t);
184
185	*size = options_get_number(&s->options, "status-left-length");
186	leftlen = screen_write_cstrlen(utf8flag, "%s", left);
187	if (leftlen < *size)
188		*size = leftlen;
189	return (left);
190}
191
192/* Retrieve options for right string. */
193char *
194status_redraw_get_right(struct client *c, time_t t, int utf8flag,
195    struct grid_cell *gc, size_t *size)
196{
197	struct session	*s = c->session;
198	const char	*template;
199	char		*right;
200	size_t		 rightlen;
201
202	style_apply_update(gc, &s->options, "status-right-style");
203
204	template = options_get_string(&s->options, "status-right");
205	right = status_replace(c, NULL, template, t);
206
207	*size = options_get_number(&s->options, "status-right-length");
208	rightlen = screen_write_cstrlen(utf8flag, "%s", right);
209	if (rightlen < *size)
210		*size = rightlen;
211	return (right);
212}
213
214/* Get window at window list position. */
215struct window *
216status_get_window_at(struct client *c, u_int x)
217{
218	struct session	*s = c->session;
219	struct winlink	*wl;
220	struct options	*oo;
221	size_t		 len;
222
223	x += c->wlmouse;
224	RB_FOREACH(wl, winlinks, &s->windows) {
225		oo = &wl->window->options;
226		len = strlen(options_get_string(oo, "window-status-separator"));
227
228		if (x < wl->status_width)
229			return (wl->window);
230		x -= wl->status_width + len;
231	}
232	return (NULL);
233}
234
235/* Draw status for client on the last lines of given context. */
236int
237status_redraw(struct client *c)
238{
239	struct screen_write_ctx	ctx;
240	struct session	       *s = c->session;
241	struct winlink	       *wl;
242	struct screen		old_status, window_list;
243	struct grid_cell	stdgc, lgc, rgc, gc;
244	struct options	       *oo;
245	time_t			t;
246	char		       *left, *right, *sep;
247	u_int			offset, needed;
248	u_int			wlstart, wlwidth, wlavailable, wloffset, wlsize;
249	size_t			llen, rlen, seplen;
250	int			larrow, rarrow, utf8flag;
251
252	/* No status line? */
253	if (c->tty.sy == 0 || !options_get_number(&s->options, "status"))
254		return (1);
255	left = right = NULL;
256	larrow = rarrow = 0;
257
258	/* Update status timer. */
259	if (gettimeofday(&c->status_timer, NULL) != 0)
260		fatal("gettimeofday failed");
261	t = c->status_timer.tv_sec;
262
263	/* Set up default colour. */
264	style_apply(&stdgc, &s->options, "status-style");
265
266	/* Create the target screen. */
267	memcpy(&old_status, &c->status, sizeof old_status);
268	screen_init(&c->status, c->tty.sx, 1, 0);
269	screen_write_start(&ctx, NULL, &c->status);
270	for (offset = 0; offset < c->tty.sx; offset++)
271		screen_write_putc(&ctx, &stdgc, ' ');
272	screen_write_stop(&ctx);
273
274	/* If the height is one line, blank status line. */
275	if (c->tty.sy <= 1)
276		goto out;
277
278	/* Get UTF-8 flag. */
279	utf8flag = options_get_number(&s->options, "status-utf8");
280
281	/* Work out left and right strings. */
282	memcpy(&lgc, &stdgc, sizeof lgc);
283	left = status_redraw_get_left(c, t, utf8flag, &lgc, &llen);
284	memcpy(&rgc, &stdgc, sizeof rgc);
285	right = status_redraw_get_right(c, t, utf8flag, &rgc, &rlen);
286
287	/*
288	 * Figure out how much space we have for the window list. If there
289	 * isn't enough space, just show a blank status line.
290	 */
291	needed = 0;
292	if (llen != 0)
293		needed += llen;
294	if (rlen != 0)
295		needed += rlen;
296	if (c->tty.sx == 0 || c->tty.sx <= needed)
297		goto out;
298	wlavailable = c->tty.sx - needed;
299
300	/* Calculate the total size needed for the window list. */
301	wlstart = wloffset = wlwidth = 0;
302	RB_FOREACH(wl, winlinks, &s->windows) {
303		free(wl->status_text);
304		memcpy(&wl->status_cell, &stdgc, sizeof wl->status_cell);
305		wl->status_text = status_print(c, wl, t, &wl->status_cell);
306		wl->status_width =
307		    screen_write_cstrlen(utf8flag, "%s", wl->status_text);
308
309		if (wl == s->curw)
310			wloffset = wlwidth;
311
312		oo = &wl->window->options;
313		sep = options_get_string(oo, "window-status-separator");
314		seplen = screen_write_strlen(utf8flag, "%s", sep);
315		wlwidth += wl->status_width + seplen;
316	}
317
318	/* Create a new screen for the window list. */
319	screen_init(&window_list, wlwidth, 1, 0);
320
321	/* And draw the window list into it. */
322	screen_write_start(&ctx, NULL, &window_list);
323	RB_FOREACH(wl, winlinks, &s->windows) {
324		screen_write_cnputs(&ctx,
325		    -1, &wl->status_cell, utf8flag, "%s", wl->status_text);
326
327		oo = &wl->window->options;
328		sep = options_get_string(oo, "window-status-separator");
329		screen_write_nputs(&ctx, -1, &stdgc, utf8flag, "%s", sep);
330	}
331	screen_write_stop(&ctx);
332
333	/* If there is enough space for the total width, skip to draw now. */
334	if (wlwidth <= wlavailable)
335		goto draw;
336
337	/* Find size of current window text. */
338	wlsize = s->curw->status_width;
339
340	/*
341	 * If the current window is already on screen, good to draw from the
342	 * start and just leave off the end.
343	 */
344	if (wloffset + wlsize < wlavailable) {
345		if (wlavailable > 0) {
346			rarrow = 1;
347			wlavailable--;
348		}
349		wlwidth = wlavailable;
350	} else {
351		/*
352		 * Work out how many characters we need to omit from the
353		 * start. There are wlavailable characters to fill, and
354		 * wloffset + wlsize must be the last. So, the start character
355		 * is wloffset + wlsize - wlavailable.
356		 */
357		if (wlavailable > 0) {
358			larrow = 1;
359			wlavailable--;
360		}
361
362		wlstart = wloffset + wlsize - wlavailable;
363		if (wlavailable > 0 && wlwidth > wlstart + wlavailable + 1) {
364			rarrow = 1;
365			wlstart++;
366			wlavailable--;
367		}
368		wlwidth = wlavailable;
369	}
370
371	/* Bail if anything is now too small too. */
372	if (wlwidth == 0 || wlavailable == 0) {
373		screen_free(&window_list);
374		goto out;
375	}
376
377	/*
378	 * Now the start position is known, work out the state of the left and
379	 * right arrows.
380	 */
381	offset = 0;
382	RB_FOREACH(wl, winlinks, &s->windows) {
383		if (wl->flags & WINLINK_ALERTFLAGS &&
384		    larrow == 1 && offset < wlstart)
385			larrow = -1;
386
387		offset += wl->status_width;
388
389		if (wl->flags & WINLINK_ALERTFLAGS &&
390		    rarrow == 1 && offset > wlstart + wlwidth)
391			rarrow = -1;
392	}
393
394draw:
395	/* Begin drawing. */
396	screen_write_start(&ctx, NULL, &c->status);
397
398	/* Draw the left string and arrow. */
399	screen_write_cursormove(&ctx, 0, 0);
400	if (llen != 0)
401		screen_write_cnputs(&ctx, llen, &lgc, utf8flag, "%s", left);
402	if (larrow != 0) {
403		memcpy(&gc, &stdgc, sizeof gc);
404		if (larrow == -1)
405			gc.attr ^= GRID_ATTR_REVERSE;
406		screen_write_putc(&ctx, &gc, '<');
407	}
408
409	/* Draw the right string and arrow. */
410	if (rarrow != 0) {
411		screen_write_cursormove(&ctx, c->tty.sx - rlen - 1, 0);
412		memcpy(&gc, &stdgc, sizeof gc);
413		if (rarrow == -1)
414			gc.attr ^= GRID_ATTR_REVERSE;
415		screen_write_putc(&ctx, &gc, '>');
416	} else
417		screen_write_cursormove(&ctx, c->tty.sx - rlen, 0);
418	if (rlen != 0)
419		screen_write_cnputs(&ctx, rlen, &rgc, utf8flag, "%s", right);
420
421	/* Figure out the offset for the window list. */
422	if (llen != 0)
423		wloffset = llen;
424	else
425		wloffset = 0;
426	if (wlwidth < wlavailable) {
427		switch (options_get_number(&s->options, "status-justify")) {
428		case 1:	/* centred */
429			wloffset += (wlavailable - wlwidth) / 2;
430			break;
431		case 2:	/* right */
432			wloffset += (wlavailable - wlwidth);
433			break;
434		}
435	}
436	if (larrow != 0)
437		wloffset++;
438
439	/* Copy the window list. */
440	c->wlmouse = -wloffset + wlstart;
441	screen_write_cursormove(&ctx, wloffset, 0);
442	screen_write_copy(&ctx, &window_list, wlstart, 0, wlwidth, 1);
443	screen_free(&window_list);
444
445	screen_write_stop(&ctx);
446
447out:
448	free(left);
449	free(right);
450
451	if (grid_compare(c->status.grid, old_status.grid) == 0) {
452		screen_free(&old_status);
453		return (0);
454	}
455	screen_free(&old_status);
456	return (1);
457}
458
459/* Replace special sequences in fmt. */
460char *
461status_replace(struct client *c, struct winlink *wl, const char *fmt, time_t t)
462{
463	struct format_tree	*ft;
464	char			*expanded;
465
466	if (fmt == NULL)
467		return (xstrdup(""));
468
469	ft = format_create_status(1);
470	format_defaults(ft, c, NULL, wl, NULL);
471
472	expanded = format_expand_time(ft, fmt, t);
473
474	format_free(ft);
475	return (expanded);
476}
477
478/* Return winlink status line entry and adjust gc as necessary. */
479char *
480status_print(struct client *c, struct winlink *wl, time_t t,
481    struct grid_cell *gc)
482{
483	struct options	*oo = &wl->window->options;
484	struct session	*s = c->session;
485	const char	*fmt;
486	char   		*text;
487
488	style_apply_update(gc, oo, "window-status-style");
489	fmt = options_get_string(oo, "window-status-format");
490	if (wl == s->curw) {
491		style_apply_update(gc, oo, "window-status-current-style");
492		fmt = options_get_string(oo, "window-status-current-format");
493	}
494	if (wl == TAILQ_FIRST(&s->lastw))
495		style_apply_update(gc, oo, "window-status-last-style");
496
497	if (wl->flags & WINLINK_BELL)
498		style_apply_update(gc, oo, "window-status-bell-style");
499	else if (wl->flags & (WINLINK_ACTIVITY|WINLINK_SILENCE))
500		style_apply_update(gc, oo, "window-status-activity-style");
501
502	text = status_replace(c, wl, fmt, t);
503	return (text);
504}
505
506/* Set a status line message. */
507void
508status_message_set(struct client *c, const char *fmt, ...)
509{
510	struct timeval		 tv;
511	struct message_entry	*msg, *msg1;
512	va_list			 ap;
513	int			 delay;
514	u_int			 first, limit;
515
516	limit = options_get_number(&global_options, "message-limit");
517
518	status_prompt_clear(c);
519	status_message_clear(c);
520
521	va_start(ap, fmt);
522	xvasprintf(&c->message_string, fmt, ap);
523	va_end(ap);
524
525	msg = xcalloc(1, sizeof *msg);
526	msg->msg_time = time(NULL);
527	msg->msg_num = c->message_next++;
528	msg->msg = xstrdup(c->message_string);
529	TAILQ_INSERT_TAIL(&c->message_log, msg, entry);
530
531	first = c->message_next - limit;
532	TAILQ_FOREACH_SAFE(msg, &c->message_log, entry, msg1) {
533		if (msg->msg_num >= first)
534			continue;
535		free(msg->msg);
536		TAILQ_REMOVE(&c->message_log, msg, entry);
537		free(msg);
538	}
539
540	delay = options_get_number(&c->session->options, "display-time");
541	tv.tv_sec = delay / 1000;
542	tv.tv_usec = (delay % 1000) * 1000L;
543
544	if (event_initialized(&c->message_timer))
545		evtimer_del(&c->message_timer);
546	evtimer_set(&c->message_timer, status_message_callback, c);
547	evtimer_add(&c->message_timer, &tv);
548
549	c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
550	c->flags |= CLIENT_STATUS;
551}
552
553/* Clear status line message. */
554void
555status_message_clear(struct client *c)
556{
557	if (c->message_string == NULL)
558		return;
559
560	free(c->message_string);
561	c->message_string = NULL;
562
563	c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
564	c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
565
566	screen_reinit(&c->status);
567}
568
569/* Clear status line message after timer expires. */
570void
571status_message_callback(unused int fd, unused short event, void *data)
572{
573	struct client	*c = data;
574
575	status_message_clear(c);
576}
577
578/* Draw client message on status line of present else on last line. */
579int
580status_message_redraw(struct client *c)
581{
582	struct screen_write_ctx		ctx;
583	struct session		       *s = c->session;
584	struct screen		        old_status;
585	size_t			        len;
586	struct grid_cell		gc;
587	int				utf8flag;
588
589	if (c->tty.sx == 0 || c->tty.sy == 0)
590		return (0);
591	memcpy(&old_status, &c->status, sizeof old_status);
592	screen_init(&c->status, c->tty.sx, 1, 0);
593
594	utf8flag = options_get_number(&s->options, "status-utf8");
595
596	len = screen_write_strlen(utf8flag, "%s", c->message_string);
597	if (len > c->tty.sx)
598		len = c->tty.sx;
599
600	style_apply(&gc, &s->options, "message-style");
601
602	screen_write_start(&ctx, NULL, &c->status);
603
604	screen_write_cursormove(&ctx, 0, 0);
605	screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->message_string);
606	for (; len < c->tty.sx; len++)
607		screen_write_putc(&ctx, &gc, ' ');
608
609	screen_write_stop(&ctx);
610
611	if (grid_compare(c->status.grid, old_status.grid) == 0) {
612		screen_free(&old_status);
613		return (0);
614	}
615	screen_free(&old_status);
616	return (1);
617}
618
619/* Enable status line prompt. */
620void
621status_prompt_set(struct client *c, const char *msg, const char *input,
622    int (*callbackfn)(void *, const char *), void (*freefn)(void *),
623    void *data, int flags)
624{
625	struct format_tree	*ft;
626	int			 keys;
627	time_t			 t;
628
629	ft = format_create();
630	format_defaults(ft, c, NULL, NULL, NULL);
631	t = time(NULL);
632
633	status_message_clear(c);
634	status_prompt_clear(c);
635
636	c->prompt_string = format_expand_time(ft, msg, t);
637
638	c->prompt_buffer = format_expand_time(ft, input, t);
639	c->prompt_index = strlen(c->prompt_buffer);
640
641	c->prompt_callbackfn = callbackfn;
642	c->prompt_freefn = freefn;
643	c->prompt_data = data;
644
645	c->prompt_hindex = 0;
646
647	c->prompt_flags = flags;
648
649	keys = options_get_number(&c->session->options, "status-keys");
650	if (keys == MODEKEY_EMACS)
651		mode_key_init(&c->prompt_mdata, &mode_key_tree_emacs_edit);
652	else
653		mode_key_init(&c->prompt_mdata, &mode_key_tree_vi_edit);
654
655	c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
656	c->flags |= CLIENT_STATUS;
657
658	format_free(ft);
659}
660
661/* Remove status line prompt. */
662void
663status_prompt_clear(struct client *c)
664{
665	if (c->prompt_string == NULL)
666		return;
667
668	if (c->prompt_freefn != NULL && c->prompt_data != NULL)
669		c->prompt_freefn(c->prompt_data);
670
671	free(c->prompt_string);
672	c->prompt_string = NULL;
673
674	free(c->prompt_buffer);
675	c->prompt_buffer = NULL;
676
677	c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
678	c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
679
680	screen_reinit(&c->status);
681}
682
683/* Update status line prompt with a new prompt string. */
684void
685status_prompt_update(struct client *c, const char *msg, const char *input)
686{
687	struct format_tree	*ft;
688	time_t			 t;
689
690	ft = format_create();
691	format_defaults(ft, c, NULL, NULL, NULL);
692	t = time(NULL);
693
694	free(c->prompt_string);
695	c->prompt_string = format_expand_time(ft, msg, t);
696
697	free(c->prompt_buffer);
698	c->prompt_buffer = format_expand_time(ft, input, t);
699	c->prompt_index = strlen(c->prompt_buffer);
700
701	c->prompt_hindex = 0;
702
703	c->flags |= CLIENT_STATUS;
704
705	format_free(ft);
706}
707
708/* Draw client prompt on status line of present else on last line. */
709int
710status_prompt_redraw(struct client *c)
711{
712	struct screen_write_ctx		ctx;
713	struct session		       *s = c->session;
714	struct screen		        old_status;
715	size_t			        i, size, left, len, off;
716	struct grid_cell		gc, *gcp;
717	int				utf8flag;
718
719	if (c->tty.sx == 0 || c->tty.sy == 0)
720		return (0);
721	memcpy(&old_status, &c->status, sizeof old_status);
722	screen_init(&c->status, c->tty.sx, 1, 0);
723
724	utf8flag = options_get_number(&s->options, "status-utf8");
725
726	len = screen_write_strlen(utf8flag, "%s", c->prompt_string);
727	if (len > c->tty.sx)
728		len = c->tty.sx;
729	off = 0;
730
731	/* Change colours for command mode. */
732	if (c->prompt_mdata.mode == 1)
733		style_apply(&gc, &s->options, "message-command-style");
734	else
735		style_apply(&gc, &s->options, "message-style");
736
737	screen_write_start(&ctx, NULL, &c->status);
738
739	screen_write_cursormove(&ctx, 0, 0);
740	screen_write_nputs(&ctx, len, &gc, utf8flag, "%s", c->prompt_string);
741
742	left = c->tty.sx - len;
743	if (left != 0) {
744		size = screen_write_strlen(utf8flag, "%s", c->prompt_buffer);
745		if (c->prompt_index >= left) {
746			off = c->prompt_index - left + 1;
747			if (c->prompt_index == size)
748				left--;
749			size = left;
750		}
751		screen_write_nputs(
752		    &ctx, left, &gc, utf8flag, "%s", c->prompt_buffer + off);
753
754		for (i = len + size; i < c->tty.sx; i++)
755			screen_write_putc(&ctx, &gc, ' ');
756	}
757
758	screen_write_stop(&ctx);
759
760	/* Apply fake cursor. */
761	off = len + c->prompt_index - off;
762	gcp = grid_view_get_cell(c->status.grid, off, 0);
763	gcp->attr ^= GRID_ATTR_REVERSE;
764
765	if (grid_compare(c->status.grid, old_status.grid) == 0) {
766		screen_free(&old_status);
767		return (0);
768	}
769	screen_free(&old_status);
770	return (1);
771}
772
773/* Handle keys in prompt. */
774void
775status_prompt_key(struct client *c, int key)
776{
777	struct session		*sess = c->session;
778	struct options		*oo = &sess->options;
779	struct paste_buffer	*pb;
780	char			*s, *first, *last, word[64], swapc;
781	const char		*histstr;
782	const char		*wsep = NULL;
783	u_char			 ch;
784	size_t			 size, n, off, idx;
785
786	size = strlen(c->prompt_buffer);
787	switch (mode_key_lookup(&c->prompt_mdata, key, NULL)) {
788	case MODEKEYEDIT_CURSORLEFT:
789		if (c->prompt_index > 0) {
790			c->prompt_index--;
791			c->flags |= CLIENT_STATUS;
792		}
793		break;
794	case MODEKEYEDIT_SWITCHMODE:
795		c->flags |= CLIENT_STATUS;
796		break;
797	case MODEKEYEDIT_SWITCHMODEAPPEND:
798		c->flags |= CLIENT_STATUS;
799		/* FALLTHROUGH */
800	case MODEKEYEDIT_CURSORRIGHT:
801		if (c->prompt_index < size) {
802			c->prompt_index++;
803			c->flags |= CLIENT_STATUS;
804		}
805		break;
806	case MODEKEYEDIT_SWITCHMODEBEGINLINE:
807		c->flags |= CLIENT_STATUS;
808		/* FALLTHROUGH */
809	case MODEKEYEDIT_STARTOFLINE:
810		if (c->prompt_index != 0) {
811			c->prompt_index = 0;
812			c->flags |= CLIENT_STATUS;
813		}
814		break;
815	case MODEKEYEDIT_SWITCHMODEAPPENDLINE:
816		c->flags |= CLIENT_STATUS;
817		/* FALLTHROUGH */
818	case MODEKEYEDIT_ENDOFLINE:
819		if (c->prompt_index != size) {
820			c->prompt_index = size;
821			c->flags |= CLIENT_STATUS;
822		}
823		break;
824	case MODEKEYEDIT_COMPLETE:
825		if (*c->prompt_buffer == '\0')
826			break;
827
828		idx = c->prompt_index;
829		if (idx != 0)
830			idx--;
831
832		/* Find the word we are in. */
833		first = c->prompt_buffer + idx;
834		while (first > c->prompt_buffer && *first != ' ')
835			first--;
836		while (*first == ' ')
837			first++;
838		last = c->prompt_buffer + idx;
839		while (*last != '\0' && *last != ' ')
840			last++;
841		while (*last == ' ')
842			last--;
843		if (*last != '\0')
844			last++;
845		if (last <= first ||
846		    ((size_t) (last - first)) > (sizeof word) - 1)
847			break;
848		memcpy(word, first, last - first);
849		word[last - first] = '\0';
850
851		/* And try to complete it. */
852		if ((s = status_prompt_complete(sess, word)) == NULL)
853			break;
854
855		/* Trim out word. */
856		n = size - (last - c->prompt_buffer) + 1; /* with \0 */
857		memmove(first, last, n);
858		size -= last - first;
859
860		/* Insert the new word. */
861		size += strlen(s);
862		off = first - c->prompt_buffer;
863		c->prompt_buffer = xrealloc(c->prompt_buffer, size + 1);
864		first = c->prompt_buffer + off;
865		memmove(first + strlen(s), first, n);
866		memcpy(first, s, strlen(s));
867
868		c->prompt_index = (first - c->prompt_buffer) + strlen(s);
869		free(s);
870
871		c->flags |= CLIENT_STATUS;
872		break;
873	case MODEKEYEDIT_BACKSPACE:
874		if (c->prompt_index != 0) {
875			if (c->prompt_index == size)
876				c->prompt_buffer[--c->prompt_index] = '\0';
877			else {
878				memmove(c->prompt_buffer + c->prompt_index - 1,
879				    c->prompt_buffer + c->prompt_index,
880				    size + 1 - c->prompt_index);
881				c->prompt_index--;
882			}
883			c->flags |= CLIENT_STATUS;
884		}
885		break;
886	case MODEKEYEDIT_DELETE:
887	case MODEKEYEDIT_SWITCHMODESUBSTITUTE:
888		if (c->prompt_index != size) {
889			memmove(c->prompt_buffer + c->prompt_index,
890			    c->prompt_buffer + c->prompt_index + 1,
891			    size + 1 - c->prompt_index);
892			c->flags |= CLIENT_STATUS;
893		}
894		break;
895	case MODEKEYEDIT_DELETELINE:
896	case MODEKEYEDIT_SWITCHMODESUBSTITUTELINE:
897		*c->prompt_buffer = '\0';
898		c->prompt_index = 0;
899		c->flags |= CLIENT_STATUS;
900		break;
901	case MODEKEYEDIT_DELETETOENDOFLINE:
902	case MODEKEYEDIT_SWITCHMODECHANGELINE:
903		if (c->prompt_index < size) {
904			c->prompt_buffer[c->prompt_index] = '\0';
905			c->flags |= CLIENT_STATUS;
906		}
907		break;
908	case MODEKEYEDIT_DELETEWORD:
909		wsep = options_get_string(oo, "word-separators");
910		idx = c->prompt_index;
911
912		/* Find a non-separator. */
913		while (idx != 0) {
914			idx--;
915			if (!strchr(wsep, c->prompt_buffer[idx]))
916				break;
917		}
918
919		/* Find the separator at the beginning of the word. */
920		while (idx != 0) {
921			idx--;
922			if (strchr(wsep, c->prompt_buffer[idx])) {
923				/* Go back to the word. */
924				idx++;
925				break;
926			}
927		}
928
929		memmove(c->prompt_buffer + idx,
930		    c->prompt_buffer + c->prompt_index,
931		    size + 1 - c->prompt_index);
932		memset(c->prompt_buffer + size - (c->prompt_index - idx),
933		    '\0', c->prompt_index - idx);
934		c->prompt_index = idx;
935		c->flags |= CLIENT_STATUS;
936		break;
937	case MODEKEYEDIT_NEXTSPACE:
938		wsep = " ";
939		/* FALLTHROUGH */
940	case MODEKEYEDIT_NEXTWORD:
941		if (wsep == NULL)
942			wsep = options_get_string(oo, "word-separators");
943
944		/* Find a separator. */
945		while (c->prompt_index != size) {
946			c->prompt_index++;
947			if (strchr(wsep, c->prompt_buffer[c->prompt_index]))
948				break;
949		}
950
951		/* Find the word right after the separation. */
952		while (c->prompt_index != size) {
953			c->prompt_index++;
954			if (!strchr(wsep, c->prompt_buffer[c->prompt_index]))
955				break;
956		}
957
958		c->flags |= CLIENT_STATUS;
959		break;
960	case MODEKEYEDIT_NEXTSPACEEND:
961		wsep = " ";
962		/* FALLTHROUGH */
963	case MODEKEYEDIT_NEXTWORDEND:
964		if (wsep == NULL)
965			wsep = options_get_string(oo, "word-separators");
966
967		/* Find a word. */
968		while (c->prompt_index != size) {
969			c->prompt_index++;
970			if (!strchr(wsep, c->prompt_buffer[c->prompt_index]))
971				break;
972		}
973
974		/* Find the separator at the end of the word. */
975		while (c->prompt_index != size) {
976			c->prompt_index++;
977			if (strchr(wsep, c->prompt_buffer[c->prompt_index]))
978				break;
979		}
980
981		/* Back up to the end-of-word like vi. */
982		if (options_get_number(oo, "status-keys") == MODEKEY_VI &&
983		    c->prompt_index != 0)
984			c->prompt_index--;
985
986		c->flags |= CLIENT_STATUS;
987		break;
988	case MODEKEYEDIT_PREVIOUSSPACE:
989		wsep = " ";
990		/* FALLTHROUGH */
991	case MODEKEYEDIT_PREVIOUSWORD:
992		if (wsep == NULL)
993			wsep = options_get_string(oo, "word-separators");
994
995		/* Find a non-separator. */
996		while (c->prompt_index != 0) {
997			c->prompt_index--;
998			if (!strchr(wsep, c->prompt_buffer[c->prompt_index]))
999				break;
1000		}
1001
1002		/* Find the separator at the beginning of the word. */
1003		while (c->prompt_index != 0) {
1004			c->prompt_index--;
1005			if (strchr(wsep, c->prompt_buffer[c->prompt_index])) {
1006				/* Go back to the word. */
1007				c->prompt_index++;
1008				break;
1009			}
1010		}
1011
1012		c->flags |= CLIENT_STATUS;
1013		break;
1014	case MODEKEYEDIT_HISTORYUP:
1015		histstr = status_prompt_up_history(&c->prompt_hindex);
1016		if (histstr == NULL)
1017			break;
1018		free(c->prompt_buffer);
1019		c->prompt_buffer = xstrdup(histstr);
1020		c->prompt_index = strlen(c->prompt_buffer);
1021		c->flags |= CLIENT_STATUS;
1022		break;
1023	case MODEKEYEDIT_HISTORYDOWN:
1024		histstr = status_prompt_down_history(&c->prompt_hindex);
1025		if (histstr == NULL)
1026			break;
1027		free(c->prompt_buffer);
1028		c->prompt_buffer = xstrdup(histstr);
1029		c->prompt_index = strlen(c->prompt_buffer);
1030		c->flags |= CLIENT_STATUS;
1031		break;
1032	case MODEKEYEDIT_PASTE:
1033		if ((pb = paste_get_top()) == NULL)
1034			break;
1035		for (n = 0; n < pb->size; n++) {
1036			ch = (u_char) pb->data[n];
1037			if (ch < 32 || ch == 127)
1038				break;
1039		}
1040
1041		c->prompt_buffer = xrealloc(c->prompt_buffer, size + n + 1);
1042		if (c->prompt_index == size) {
1043			memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
1044			c->prompt_index += n;
1045			c->prompt_buffer[c->prompt_index] = '\0';
1046		} else {
1047			memmove(c->prompt_buffer + c->prompt_index + n,
1048			    c->prompt_buffer + c->prompt_index,
1049			    size + 1 - c->prompt_index);
1050			memcpy(c->prompt_buffer + c->prompt_index, pb->data, n);
1051			c->prompt_index += n;
1052		}
1053
1054		c->flags |= CLIENT_STATUS;
1055		break;
1056	case MODEKEYEDIT_TRANSPOSECHARS:
1057		idx = c->prompt_index;
1058		if (idx < size)
1059			idx++;
1060		if (idx >= 2) {
1061			swapc = c->prompt_buffer[idx - 2];
1062			c->prompt_buffer[idx - 2] = c->prompt_buffer[idx - 1];
1063			c->prompt_buffer[idx - 1] = swapc;
1064			c->prompt_index = idx;
1065			c->flags |= CLIENT_STATUS;
1066		}
1067		break;
1068	case MODEKEYEDIT_ENTER:
1069		if (*c->prompt_buffer != '\0')
1070			status_prompt_add_history(c->prompt_buffer);
1071		if (c->prompt_callbackfn(c->prompt_data, c->prompt_buffer) == 0)
1072			status_prompt_clear(c);
1073		break;
1074	case MODEKEYEDIT_CANCEL:
1075		if (c->prompt_callbackfn(c->prompt_data, NULL) == 0)
1076			status_prompt_clear(c);
1077		break;
1078	case MODEKEY_OTHER:
1079		if ((key & 0xff00) != 0 || key < 32 || key == 127)
1080			break;
1081		c->prompt_buffer = xrealloc(c->prompt_buffer, size + 2);
1082
1083		if (c->prompt_index == size) {
1084			c->prompt_buffer[c->prompt_index++] = key;
1085			c->prompt_buffer[c->prompt_index] = '\0';
1086		} else {
1087			memmove(c->prompt_buffer + c->prompt_index + 1,
1088			    c->prompt_buffer + c->prompt_index,
1089			    size + 1 - c->prompt_index);
1090			c->prompt_buffer[c->prompt_index++] = key;
1091		}
1092
1093		if (c->prompt_flags & PROMPT_SINGLE) {
1094			if (c->prompt_callbackfn(
1095			    c->prompt_data, c->prompt_buffer) == 0)
1096				status_prompt_clear(c);
1097		}
1098
1099		c->flags |= CLIENT_STATUS;
1100		break;
1101	default:
1102		break;
1103	}
1104}
1105
1106/* Get previous line from the history. */
1107const char *
1108status_prompt_up_history(u_int *idx)
1109{
1110	/*
1111	 * History runs from 0 to size - 1. Index is from 0 to size. Zero is
1112	 * empty.
1113	 */
1114
1115	if (status_prompt_hsize == 0 || *idx == status_prompt_hsize)
1116		return (NULL);
1117	(*idx)++;
1118	return (status_prompt_hlist[status_prompt_hsize - *idx]);
1119}
1120
1121/* Get next line from the history. */
1122const char *
1123status_prompt_down_history(u_int *idx)
1124{
1125	if (status_prompt_hsize == 0 || *idx == 0)
1126		return ("");
1127	(*idx)--;
1128	if (*idx == 0)
1129		return ("");
1130	return (status_prompt_hlist[status_prompt_hsize - *idx]);
1131}
1132
1133/* Add line to the history. */
1134void
1135status_prompt_add_history(const char *line)
1136{
1137	size_t	size;
1138
1139	if (status_prompt_hsize > 0 &&
1140	    strcmp(status_prompt_hlist[status_prompt_hsize - 1], line) == 0)
1141		return;
1142
1143	if (status_prompt_hsize == PROMPT_HISTORY) {
1144		free(status_prompt_hlist[0]);
1145
1146		size = (PROMPT_HISTORY - 1) * sizeof *status_prompt_hlist;
1147		memmove(&status_prompt_hlist[0], &status_prompt_hlist[1], size);
1148
1149		status_prompt_hlist[status_prompt_hsize - 1] = xstrdup(line);
1150		return;
1151	}
1152
1153	status_prompt_hlist = xreallocarray(status_prompt_hlist,
1154	    status_prompt_hsize + 1, sizeof *status_prompt_hlist);
1155	status_prompt_hlist[status_prompt_hsize++] = xstrdup(line);
1156}
1157
1158/* Build completion list. */
1159const char **
1160status_prompt_complete_list(u_int *size, const char *s)
1161{
1162	const char				**list = NULL, **layout;
1163	const struct cmd_entry			**cmdent;
1164	const struct options_table_entry	 *oe;
1165	const char				 *layouts[] = {
1166		"even-horizontal", "even-vertical", "main-horizontal",
1167		"main-vertical", "tiled", NULL
1168	};
1169
1170	*size = 0;
1171	for (cmdent = cmd_table; *cmdent != NULL; cmdent++) {
1172		if (strncmp((*cmdent)->name, s, strlen(s)) == 0) {
1173			list = xreallocarray(list, (*size) + 1, sizeof *list);
1174			list[(*size)++] = (*cmdent)->name;
1175		}
1176	}
1177	for (oe = server_options_table; oe->name != NULL; oe++) {
1178		if (strncmp(oe->name, s, strlen(s)) == 0) {
1179			list = xreallocarray(list, (*size) + 1, sizeof *list);
1180			list[(*size)++] = oe->name;
1181		}
1182	}
1183	for (oe = session_options_table; oe->name != NULL; oe++) {
1184		if (strncmp(oe->name, s, strlen(s)) == 0) {
1185			list = xreallocarray(list, (*size) + 1, sizeof *list);
1186			list[(*size)++] = oe->name;
1187		}
1188	}
1189	for (oe = window_options_table; oe->name != NULL; oe++) {
1190		if (strncmp(oe->name, s, strlen(s)) == 0) {
1191			list = xreallocarray(list, (*size) + 1, sizeof *list);
1192			list[(*size)++] = oe->name;
1193		}
1194	}
1195	for (layout = layouts; *layout != NULL; layout++) {
1196		if (strncmp(*layout, s, strlen(s)) == 0) {
1197			list = xreallocarray(list, (*size) + 1, sizeof *list);
1198			list[(*size)++] = *layout;
1199		}
1200	}
1201	return (list);
1202}
1203
1204/* Find longest prefix. */
1205char *
1206status_prompt_complete_prefix(const char **list, u_int size)
1207{
1208	char	 *out;
1209	u_int	  i;
1210	size_t	  j;
1211
1212	out = xstrdup(list[0]);
1213	for (i = 1; i < size; i++) {
1214		j = strlen(list[i]);
1215		if (j > strlen(out))
1216			j = strlen(out);
1217		for (; j > 0; j--) {
1218			if (out[j - 1] != list[i][j - 1])
1219				out[j - 1] = '\0';
1220		}
1221	}
1222	return (out);
1223}
1224
1225/* Complete word. */
1226char *
1227status_prompt_complete(struct session *sess, const char *s)
1228{
1229	const char	**list = NULL, *colon;
1230	u_int		  size = 0, i;
1231	struct session	 *s_loop;
1232	struct winlink	 *wl;
1233	struct window	 *w;
1234	char		 *copy, *out, *tmp;
1235
1236	if (*s == '\0')
1237		return (NULL);
1238	out = NULL;
1239
1240	if (strncmp(s, "-t", 2) != 0 && strncmp(s, "-s", 2) != 0) {
1241		list = status_prompt_complete_list(&size, s);
1242		if (size == 0)
1243			out = NULL;
1244		else if (size == 1)
1245			xasprintf(&out, "%s ", list[0]);
1246		else
1247			out = status_prompt_complete_prefix(list, size);
1248		free(list);
1249		return (out);
1250	}
1251	copy = xstrdup(s);
1252
1253	colon = ":";
1254	if (copy[strlen(copy) - 1] == ':')
1255		copy[strlen(copy) - 1] = '\0';
1256	else
1257		colon = "";
1258	s = copy + 2;
1259
1260	RB_FOREACH(s_loop, sessions, &sessions) {
1261		if (strncmp(s_loop->name, s, strlen(s)) == 0) {
1262			list = xreallocarray(list, size + 2, sizeof *list);
1263			list[size++] = s_loop->name;
1264		}
1265	}
1266	if (size == 1) {
1267		out = xstrdup(list[0]);
1268		if (session_find(list[0]) != NULL)
1269			colon = ":";
1270	} else if (size != 0)
1271		out = status_prompt_complete_prefix(list, size);
1272	if (out != NULL) {
1273		xasprintf(&tmp, "-%c%s%s", copy[1], out, colon);
1274		out = tmp;
1275		goto found;
1276	}
1277
1278	colon = "";
1279	if (*s == ':') {
1280		RB_FOREACH(wl, winlinks, &sess->windows) {
1281			xasprintf(&tmp, ":%s", wl->window->name);
1282			if (strncmp(tmp, s, strlen(s)) == 0){
1283				list = xreallocarray(list, size + 1,
1284				    sizeof *list);
1285				list[size++] = tmp;
1286				continue;
1287			}
1288			free(tmp);
1289
1290			xasprintf(&tmp, ":%d", wl->idx);
1291			if (strncmp(tmp, s, strlen(s)) == 0) {
1292				list = xreallocarray(list, size + 1,
1293				    sizeof *list);
1294				list[size++] = tmp;
1295				continue;
1296			}
1297			free(tmp);
1298		}
1299	} else {
1300		RB_FOREACH(s_loop, sessions, &sessions) {
1301			RB_FOREACH(wl, winlinks, &s_loop->windows) {
1302				w = wl->window;
1303
1304				xasprintf(&tmp, "%s:%s", s_loop->name, w->name);
1305				if (strncmp(tmp, s, strlen(s)) == 0) {
1306					list = xreallocarray(list, size + 1,
1307					    sizeof *list);
1308					list[size++] = tmp;
1309					continue;
1310				}
1311				free(tmp);
1312
1313				xasprintf(&tmp, "%s:%d", s_loop->name, wl->idx);
1314				if (strncmp(tmp, s, strlen(s)) == 0) {
1315					list = xreallocarray(list, size + 1,
1316					    sizeof *list);
1317					list[size++] = tmp;
1318					continue;
1319				}
1320				free(tmp);
1321			}
1322		}
1323	}
1324	if (size == 1) {
1325		out = xstrdup(list[0]);
1326		colon = " ";
1327	} else if (size != 0)
1328		out = status_prompt_complete_prefix(list, size);
1329	if (out != NULL) {
1330		xasprintf(&tmp, "-%c%s%s", copy[1], out, colon);
1331		out = tmp;
1332	}
1333
1334	for (i = 0; i < size; i++)
1335		free((void *)list[i]);
1336
1337found:
1338	free(copy);
1339	free(list);
1340	return (out);
1341}
1342