status.c revision 1.228
1/* $OpenBSD: status.c,v 1.228 2021/10/26 12:22:23 nicm Exp $ */
2
3/*
4 * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
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
32static void	 status_message_callback(int, short, void *);
33static void	 status_timer_callback(int, short, void *);
34
35static char	*status_prompt_find_history_file(void);
36static const char *status_prompt_up_history(u_int *, u_int);
37static const char *status_prompt_down_history(u_int *, u_int);
38static void	 status_prompt_add_history(const char *, u_int);
39
40static char	*status_prompt_complete(struct client *, const char *, u_int);
41static char	*status_prompt_complete_window_menu(struct client *,
42		     struct session *, const char *, u_int, char);
43
44struct status_prompt_menu {
45	struct client	 *c;
46	u_int		  start;
47	u_int		  size;
48	char		**list;
49	char		  flag;
50};
51
52static const char	*prompt_type_strings[] = {
53	"command",
54	"search",
55	"target",
56	"window-target"
57};
58
59/* Status prompt history. */
60char		**status_prompt_hlist[PROMPT_NTYPES];
61u_int		  status_prompt_hsize[PROMPT_NTYPES];
62
63/* Find the history file to load/save from/to. */
64static char *
65status_prompt_find_history_file(void)
66{
67	const char	*home, *history_file;
68	char		*path;
69
70	history_file = options_get_string(global_options, "history-file");
71	if (*history_file == '\0')
72		return (NULL);
73	if (*history_file == '/')
74		return (xstrdup(history_file));
75
76	if (history_file[0] != '~' || history_file[1] != '/')
77		return (NULL);
78	if ((home = find_home()) == NULL)
79		return (NULL);
80	xasprintf(&path, "%s%s", home, history_file + 1);
81	return (path);
82}
83
84/* Add loaded history item to the appropriate list. */
85static void
86status_prompt_add_typed_history(char *line)
87{
88	char			*typestr;
89	enum prompt_type	 type = PROMPT_TYPE_INVALID;
90
91	typestr = strsep(&line, ":");
92	if (line != NULL)
93		type = status_prompt_type(typestr);
94	if (type == PROMPT_TYPE_INVALID) {
95		/*
96		 * Invalid types are not expected, but this provides backward
97		 * compatibility with old history files.
98		 */
99		if (line != NULL)
100			*(--line) = ':';
101		status_prompt_add_history(typestr, PROMPT_TYPE_COMMAND);
102	} else
103		status_prompt_add_history(line, type);
104}
105
106/* Load status prompt history from file. */
107void
108status_prompt_load_history(void)
109{
110	FILE	*f;
111	char	*history_file, *line, *tmp;
112	size_t	 length;
113
114	if ((history_file = status_prompt_find_history_file()) == NULL)
115		return;
116	log_debug("loading history from %s", history_file);
117
118	f = fopen(history_file, "r");
119	if (f == NULL) {
120		log_debug("%s: %s", history_file, strerror(errno));
121		free(history_file);
122		return;
123	}
124	free(history_file);
125
126	for (;;) {
127		if ((line = fgetln(f, &length)) == NULL)
128			break;
129
130		if (length > 0) {
131			if (line[length - 1] == '\n') {
132				line[length - 1] = '\0';
133				status_prompt_add_typed_history(line);
134			} else {
135				tmp = xmalloc(length + 1);
136				memcpy(tmp, line, length);
137				tmp[length] = '\0';
138				status_prompt_add_typed_history(tmp);
139				free(tmp);
140			}
141		}
142	}
143	fclose(f);
144}
145
146/* Save status prompt history to file. */
147void
148status_prompt_save_history(void)
149{
150	FILE	*f;
151	u_int	 i, type;
152	char	*history_file;
153
154	if ((history_file = status_prompt_find_history_file()) == NULL)
155		return;
156	log_debug("saving history to %s", history_file);
157
158	f = fopen(history_file, "w");
159	if (f == NULL) {
160		log_debug("%s: %s", history_file, strerror(errno));
161		free(history_file);
162		return;
163	}
164	free(history_file);
165
166	for (type = 0; type < PROMPT_NTYPES; type++) {
167		for (i = 0; i < status_prompt_hsize[type]; i++) {
168			fputs(prompt_type_strings[type], f);
169			fputc(':', f);
170			fputs(status_prompt_hlist[type][i], f);
171			fputc('\n', f);
172		}
173	}
174	fclose(f);
175
176}
177
178/* Status timer callback. */
179static void
180status_timer_callback(__unused int fd, __unused short events, void *arg)
181{
182	struct client	*c = arg;
183	struct session	*s = c->session;
184	struct timeval	 tv;
185
186	evtimer_del(&c->status.timer);
187
188	if (s == NULL)
189		return;
190
191	if (c->message_string == NULL && c->prompt_string == NULL)
192		c->flags |= CLIENT_REDRAWSTATUS;
193
194	timerclear(&tv);
195	tv.tv_sec = options_get_number(s->options, "status-interval");
196
197	if (tv.tv_sec != 0)
198		evtimer_add(&c->status.timer, &tv);
199	log_debug("client %p, status interval %d", c, (int)tv.tv_sec);
200}
201
202/* Start status timer for client. */
203void
204status_timer_start(struct client *c)
205{
206	struct session	*s = c->session;
207
208	if (event_initialized(&c->status.timer))
209		evtimer_del(&c->status.timer);
210	else
211		evtimer_set(&c->status.timer, status_timer_callback, c);
212
213	if (s != NULL && options_get_number(s->options, "status"))
214		status_timer_callback(-1, 0, c);
215}
216
217/* Start status timer for all clients. */
218void
219status_timer_start_all(void)
220{
221	struct client	*c;
222
223	TAILQ_FOREACH(c, &clients, entry)
224		status_timer_start(c);
225}
226
227/* Update status cache. */
228void
229status_update_cache(struct session *s)
230{
231	s->statuslines = options_get_number(s->options, "status");
232	if (s->statuslines == 0)
233		s->statusat = -1;
234	else if (options_get_number(s->options, "status-position") == 0)
235		s->statusat = 0;
236	else
237		s->statusat = 1;
238}
239
240/* Get screen line of status line. -1 means off. */
241int
242status_at_line(struct client *c)
243{
244	struct session	*s = c->session;
245
246	if (c->flags & (CLIENT_STATUSOFF|CLIENT_CONTROL))
247		return (-1);
248	if (s->statusat != 1)
249		return (s->statusat);
250	return (c->tty.sy - status_line_size(c));
251}
252
253/* Get size of status line for client's session. 0 means off. */
254u_int
255status_line_size(struct client *c)
256{
257	struct session	*s = c->session;
258
259	if (c->flags & (CLIENT_STATUSOFF|CLIENT_CONTROL))
260		return (0);
261	if (s == NULL)
262		return (options_get_number(global_s_options, "status"));
263	return (s->statuslines);
264}
265
266/* Get window at window list position. */
267struct style_range *
268status_get_range(struct client *c, u_int x, u_int y)
269{
270	struct status_line	*sl = &c->status;
271	struct style_range	*sr;
272
273	if (y >= nitems(sl->entries))
274		return (NULL);
275	TAILQ_FOREACH(sr, &sl->entries[y].ranges, entry) {
276		if (x >= sr->start && x < sr->end)
277			return (sr);
278	}
279	return (NULL);
280}
281
282/* Free all ranges. */
283static void
284status_free_ranges(struct style_ranges *srs)
285{
286	struct style_range	*sr, *sr1;
287
288	TAILQ_FOREACH_SAFE(sr, srs, entry, sr1) {
289		TAILQ_REMOVE(srs, sr, entry);
290		free(sr);
291	}
292}
293
294/* Save old status line. */
295static void
296status_push_screen(struct client *c)
297{
298	struct status_line *sl = &c->status;
299
300	if (sl->active == &sl->screen) {
301		sl->active = xmalloc(sizeof *sl->active);
302		screen_init(sl->active, c->tty.sx, status_line_size(c), 0);
303	}
304	sl->references++;
305}
306
307/* Restore old status line. */
308static void
309status_pop_screen(struct client *c)
310{
311	struct status_line *sl = &c->status;
312
313	if (--sl->references == 0) {
314		screen_free(sl->active);
315		free(sl->active);
316		sl->active = &sl->screen;
317	}
318}
319
320/* Initialize status line. */
321void
322status_init(struct client *c)
323{
324	struct status_line	*sl = &c->status;
325	u_int			 i;
326
327	for (i = 0; i < nitems(sl->entries); i++)
328		TAILQ_INIT(&sl->entries[i].ranges);
329
330	screen_init(&sl->screen, c->tty.sx, 1, 0);
331	sl->active = &sl->screen;
332}
333
334/* Free status line. */
335void
336status_free(struct client *c)
337{
338	struct status_line	*sl = &c->status;
339	u_int			 i;
340
341	for (i = 0; i < nitems(sl->entries); i++) {
342		status_free_ranges(&sl->entries[i].ranges);
343		free((void *)sl->entries[i].expanded);
344	}
345
346	if (event_initialized(&sl->timer))
347		evtimer_del(&sl->timer);
348
349	if (sl->active != &sl->screen) {
350		screen_free(sl->active);
351		free(sl->active);
352	}
353	screen_free(&sl->screen);
354}
355
356/* Draw status line for client. */
357int
358status_redraw(struct client *c)
359{
360	struct status_line		*sl = &c->status;
361	struct status_line_entry	*sle;
362	struct session			*s = c->session;
363	struct screen_write_ctx		 ctx;
364	struct grid_cell		 gc;
365	u_int				 lines, i, n, width = c->tty.sx;
366	int				 flags, force = 0, changed = 0, fg, bg;
367	struct options_entry		*o;
368	union options_value		*ov;
369	struct format_tree		*ft;
370	char				*expanded;
371
372	log_debug("%s enter", __func__);
373
374	/* Shouldn't get here if not the active screen. */
375	if (sl->active != &sl->screen)
376		fatalx("not the active screen");
377
378	/* No status line? */
379	lines = status_line_size(c);
380	if (c->tty.sy == 0 || lines == 0)
381		return (1);
382
383	/* Create format tree. */
384	flags = FORMAT_STATUS;
385	if (c->flags & CLIENT_STATUSFORCE)
386		flags |= FORMAT_FORCE;
387	ft = format_create(c, NULL, FORMAT_NONE, flags);
388	format_defaults(ft, c, NULL, NULL, NULL);
389
390	/* Set up default colour. */
391	style_apply(&gc, s->options, "status-style", ft);
392	fg = options_get_number(s->options, "status-fg");
393	if (!COLOUR_DEFAULT(fg))
394		gc.fg = fg;
395	bg = options_get_number(s->options, "status-bg");
396	if (!COLOUR_DEFAULT(bg))
397		gc.bg = bg;
398	if (!grid_cells_equal(&gc, &sl->style)) {
399		force = 1;
400		memcpy(&sl->style, &gc, sizeof sl->style);
401	}
402
403	/* Resize the target screen. */
404	if (screen_size_x(&sl->screen) != width ||
405	    screen_size_y(&sl->screen) != lines) {
406		screen_resize(&sl->screen, width, lines, 0);
407		changed = force = 1;
408	}
409	screen_write_start(&ctx, &sl->screen);
410
411	/* Write the status lines. */
412	o = options_get(s->options, "status-format");
413	if (o == NULL) {
414		for (n = 0; n < width * lines; n++)
415			screen_write_putc(&ctx, &gc, ' ');
416	} else {
417		for (i = 0; i < lines; i++) {
418			screen_write_cursormove(&ctx, 0, i, 0);
419
420			ov = options_array_get(o, i);
421			if (ov == NULL) {
422				for (n = 0; n < width; n++)
423					screen_write_putc(&ctx, &gc, ' ');
424				continue;
425			}
426			sle = &sl->entries[i];
427
428			expanded = format_expand_time(ft, ov->string);
429			if (!force &&
430			    sle->expanded != NULL &&
431			    strcmp(expanded, sle->expanded) == 0) {
432				free(expanded);
433				continue;
434			}
435			changed = 1;
436
437			for (n = 0; n < width; n++)
438				screen_write_putc(&ctx, &gc, ' ');
439			screen_write_cursormove(&ctx, 0, i, 0);
440
441			status_free_ranges(&sle->ranges);
442			format_draw(&ctx, &gc, width, expanded, &sle->ranges,
443			    0);
444
445			free(sle->expanded);
446			sle->expanded = expanded;
447		}
448	}
449	screen_write_stop(&ctx);
450
451	/* Free the format tree. */
452	format_free(ft);
453
454	/* Return if the status line has changed. */
455	log_debug("%s exit: force=%d, changed=%d", __func__, force, changed);
456	return (force || changed);
457}
458
459/* Set a status line message. */
460void
461status_message_set(struct client *c, int delay, int ignore_styles,
462    int ignore_keys, const char *fmt, ...)
463{
464	struct timeval	tv;
465	va_list		ap;
466
467	status_message_clear(c);
468	status_push_screen(c);
469
470	va_start(ap, fmt);
471	xvasprintf(&c->message_string, fmt, ap);
472	va_end(ap);
473
474	server_add_message("%s message: %s", c->name, c->message_string);
475
476	/*
477	 * With delay -1, the display-time option is used; zero means wait for
478	 * key press; more than zero is the actual delay time in milliseconds.
479	 */
480	if (delay == -1)
481		delay = options_get_number(c->session->options, "display-time");
482	if (delay > 0) {
483		tv.tv_sec = delay / 1000;
484		tv.tv_usec = (delay % 1000) * 1000L;
485
486		if (event_initialized(&c->message_timer))
487			evtimer_del(&c->message_timer);
488		evtimer_set(&c->message_timer, status_message_callback, c);
489
490		evtimer_add(&c->message_timer, &tv);
491	}
492
493	if (delay != 0)
494		c->message_ignore_keys = ignore_keys;
495	c->message_ignore_styles = ignore_styles;
496
497	c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
498	c->flags |= CLIENT_REDRAWSTATUS;
499}
500
501/* Clear status line message. */
502void
503status_message_clear(struct client *c)
504{
505	if (c->message_string == NULL)
506		return;
507
508	free(c->message_string);
509	c->message_string = NULL;
510
511	if (c->prompt_string == NULL)
512		c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
513	c->flags |= CLIENT_ALLREDRAWFLAGS; /* was frozen and may have changed */
514
515	status_pop_screen(c);
516}
517
518/* Clear status line message after timer expires. */
519static void
520status_message_callback(__unused int fd, __unused short event, void *data)
521{
522	struct client	*c = data;
523
524	status_message_clear(c);
525}
526
527/* Draw client message on status line of present else on last line. */
528int
529status_message_redraw(struct client *c)
530{
531	struct status_line	*sl = &c->status;
532	struct screen_write_ctx	 ctx;
533	struct session		*s = c->session;
534	struct screen		 old_screen;
535	size_t			 len;
536	u_int			 lines, offset;
537	struct grid_cell	 gc;
538	struct format_tree	*ft;
539
540	if (c->tty.sx == 0 || c->tty.sy == 0)
541		return (0);
542	memcpy(&old_screen, sl->active, sizeof old_screen);
543
544	lines = status_line_size(c);
545	if (lines <= 1)
546		lines = 1;
547	screen_init(sl->active, c->tty.sx, lines, 0);
548
549	len = screen_write_strlen("%s", c->message_string);
550	if (len > c->tty.sx)
551		len = c->tty.sx;
552
553	ft = format_create_defaults(NULL, c, NULL, NULL, NULL);
554	style_apply(&gc, s->options, "message-style", ft);
555	format_free(ft);
556
557	screen_write_start(&ctx, sl->active);
558	screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines - 1);
559	screen_write_cursormove(&ctx, 0, lines - 1, 0);
560	for (offset = 0; offset < c->tty.sx; offset++)
561		screen_write_putc(&ctx, &gc, ' ');
562	screen_write_cursormove(&ctx, 0, lines - 1, 0);
563	if (c->message_ignore_styles)
564		screen_write_nputs(&ctx, len, &gc, "%s", c->message_string);
565	else
566		format_draw(&ctx, &gc, c->tty.sx, c->message_string, NULL, 0);
567	screen_write_stop(&ctx);
568
569	if (grid_compare(sl->active->grid, old_screen.grid) == 0) {
570		screen_free(&old_screen);
571		return (0);
572	}
573	screen_free(&old_screen);
574	return (1);
575}
576
577/* Enable status line prompt. */
578void
579status_prompt_set(struct client *c, struct cmd_find_state *fs,
580    const char *msg, const char *input, prompt_input_cb inputcb,
581    prompt_free_cb freecb, void *data, int flags, enum prompt_type prompt_type)
582{
583	struct format_tree	*ft;
584	char			*tmp;
585
586	if (fs != NULL)
587		ft = format_create_from_state(NULL, c, fs);
588	else
589		ft = format_create_defaults(NULL, c, NULL, NULL, NULL);
590
591	if (input == NULL)
592		input = "";
593	if (flags & PROMPT_NOFORMAT)
594		tmp = xstrdup(input);
595	else
596		tmp = format_expand_time(ft, input);
597
598	status_message_clear(c);
599	status_prompt_clear(c);
600	status_push_screen(c);
601
602	c->prompt_string = format_expand_time(ft, msg);
603
604	if (flags & PROMPT_INCREMENTAL) {
605		c->prompt_last = xstrdup(tmp);
606		c->prompt_buffer = utf8_fromcstr("");
607	} else {
608		c->prompt_last = NULL;
609		c->prompt_buffer = utf8_fromcstr(tmp);
610	}
611	c->prompt_index = utf8_strlen(c->prompt_buffer);
612
613	c->prompt_inputcb = inputcb;
614	c->prompt_freecb = freecb;
615	c->prompt_data = data;
616
617	memset(c->prompt_hindex, 0, sizeof c->prompt_hindex);
618
619	c->prompt_flags = flags;
620	c->prompt_type = prompt_type;
621	c->prompt_mode = PROMPT_ENTRY;
622
623	if (~flags & PROMPT_INCREMENTAL)
624		c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
625	c->flags |= CLIENT_REDRAWSTATUS;
626
627	if (flags & PROMPT_INCREMENTAL)
628		c->prompt_inputcb(c, c->prompt_data, "=", 0);
629
630	free(tmp);
631	format_free(ft);
632}
633
634/* Remove status line prompt. */
635void
636status_prompt_clear(struct client *c)
637{
638	if (c->prompt_string == NULL)
639		return;
640
641	if (c->prompt_freecb != NULL && c->prompt_data != NULL)
642		c->prompt_freecb(c->prompt_data);
643
644	free(c->prompt_last);
645	c->prompt_last = NULL;
646
647	free(c->prompt_string);
648	c->prompt_string = NULL;
649
650	free(c->prompt_buffer);
651	c->prompt_buffer = NULL;
652
653	free(c->prompt_saved);
654	c->prompt_saved = NULL;
655
656	c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
657	c->flags |= CLIENT_ALLREDRAWFLAGS; /* was frozen and may have changed */
658
659	status_pop_screen(c);
660}
661
662/* Update status line prompt with a new prompt string. */
663void
664status_prompt_update(struct client *c, const char *msg, const char *input)
665{
666	struct format_tree	*ft;
667	char			*tmp;
668
669	ft = format_create(c, NULL, FORMAT_NONE, 0);
670	format_defaults(ft, c, NULL, NULL, NULL);
671
672	tmp = format_expand_time(ft, input);
673
674	free(c->prompt_string);
675	c->prompt_string = format_expand_time(ft, msg);
676
677	free(c->prompt_buffer);
678	c->prompt_buffer = utf8_fromcstr(tmp);
679	c->prompt_index = utf8_strlen(c->prompt_buffer);
680
681	memset(c->prompt_hindex, 0, sizeof c->prompt_hindex);
682
683	c->flags |= CLIENT_REDRAWSTATUS;
684
685	free(tmp);
686	format_free(ft);
687}
688
689/* Draw client prompt on status line of present else on last line. */
690int
691status_prompt_redraw(struct client *c)
692{
693	struct status_line	*sl = &c->status;
694	struct screen_write_ctx	 ctx;
695	struct session		*s = c->session;
696	struct screen		 old_screen;
697	u_int			 i, lines, offset, left, start, width;
698	u_int			 pcursor, pwidth;
699	struct grid_cell	 gc, cursorgc;
700	struct format_tree	*ft;
701
702	if (c->tty.sx == 0 || c->tty.sy == 0)
703		return (0);
704	memcpy(&old_screen, sl->active, sizeof old_screen);
705
706	lines = status_line_size(c);
707	if (lines <= 1)
708		lines = 1;
709	screen_init(sl->active, c->tty.sx, lines, 0);
710
711	ft = format_create_defaults(NULL, c, NULL, NULL, NULL);
712	if (c->prompt_mode == PROMPT_COMMAND)
713		style_apply(&gc, s->options, "message-command-style", ft);
714	else
715		style_apply(&gc, s->options, "message-style", ft);
716	format_free(ft);
717
718	memcpy(&cursorgc, &gc, sizeof cursorgc);
719	cursorgc.attr ^= GRID_ATTR_REVERSE;
720
721	start = screen_write_strlen("%s", c->prompt_string);
722	if (start > c->tty.sx)
723		start = c->tty.sx;
724
725	screen_write_start(&ctx, sl->active);
726	screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines - 1);
727	screen_write_cursormove(&ctx, 0, lines - 1, 0);
728	for (offset = 0; offset < c->tty.sx; offset++)
729		screen_write_putc(&ctx, &gc, ' ');
730	screen_write_cursormove(&ctx, 0, lines - 1, 0);
731	screen_write_nputs(&ctx, start, &gc, "%s", c->prompt_string);
732	screen_write_cursormove(&ctx, start, lines - 1, 0);
733
734	left = c->tty.sx - start;
735	if (left == 0)
736		goto finished;
737
738	pcursor = utf8_strwidth(c->prompt_buffer, c->prompt_index);
739	pwidth = utf8_strwidth(c->prompt_buffer, -1);
740	if (pcursor >= left) {
741		/*
742		 * The cursor would be outside the screen so start drawing
743		 * with it on the right.
744		 */
745		offset = (pcursor - left) + 1;
746		pwidth = left;
747	} else
748		offset = 0;
749	if (pwidth > left)
750		pwidth = left;
751
752	width = 0;
753	for (i = 0; c->prompt_buffer[i].size != 0; i++) {
754		if (width < offset) {
755			width += c->prompt_buffer[i].width;
756			continue;
757		}
758		if (width >= offset + pwidth)
759			break;
760		width += c->prompt_buffer[i].width;
761		if (width > offset + pwidth)
762			break;
763
764		if (i != c->prompt_index) {
765			utf8_copy(&gc.data, &c->prompt_buffer[i]);
766			screen_write_cell(&ctx, &gc);
767		} else {
768			utf8_copy(&cursorgc.data, &c->prompt_buffer[i]);
769			screen_write_cell(&ctx, &cursorgc);
770		}
771	}
772	if (sl->active->cx < screen_size_x(sl->active) && c->prompt_index >= i)
773		screen_write_putc(&ctx, &cursorgc, ' ');
774
775finished:
776	screen_write_stop(&ctx);
777
778	if (grid_compare(sl->active->grid, old_screen.grid) == 0) {
779		screen_free(&old_screen);
780		return (0);
781	}
782	screen_free(&old_screen);
783	return (1);
784}
785
786/* Is this a separator? */
787static int
788status_prompt_in_list(const char *ws, const struct utf8_data *ud)
789{
790	if (ud->size != 1 || ud->width != 1)
791		return (0);
792	return (strchr(ws, *ud->data) != NULL);
793}
794
795/* Is this a space? */
796static int
797status_prompt_space(const struct utf8_data *ud)
798{
799	if (ud->size != 1 || ud->width != 1)
800		return (0);
801	return (*ud->data == ' ');
802}
803
804/*
805 * Translate key from vi to emacs. Return 0 to drop key, 1 to process the key
806 * as an emacs key; return 2 to append to the buffer.
807 */
808static int
809status_prompt_translate_key(struct client *c, key_code key, key_code *new_key)
810{
811	if (c->prompt_mode == PROMPT_ENTRY) {
812		switch (key) {
813		case '\003': /* C-c */
814		case '\007': /* C-g */
815		case '\010': /* C-h */
816		case '\011': /* Tab */
817		case '\025': /* C-u */
818		case '\027': /* C-w */
819		case '\n':
820		case '\r':
821		case KEYC_BSPACE:
822		case KEYC_DC:
823		case KEYC_DOWN:
824		case KEYC_END:
825		case KEYC_HOME:
826		case KEYC_LEFT:
827		case KEYC_RIGHT:
828		case KEYC_UP:
829			*new_key = key;
830			return (1);
831		case '\033': /* Escape */
832			c->prompt_mode = PROMPT_COMMAND;
833			c->flags |= CLIENT_REDRAWSTATUS;
834			return (0);
835		}
836		*new_key = key;
837		return (2);
838	}
839
840	switch (key) {
841	case 'A':
842	case 'I':
843	case 'C':
844	case 's':
845	case 'a':
846		c->prompt_mode = PROMPT_ENTRY;
847		c->flags |= CLIENT_REDRAWSTATUS;
848		break; /* switch mode and... */
849	case 'S':
850		c->prompt_mode = PROMPT_ENTRY;
851		c->flags |= CLIENT_REDRAWSTATUS;
852		*new_key = '\025'; /* C-u */
853		return (1);
854	case 'i':
855	case '\033': /* Escape */
856		c->prompt_mode = PROMPT_ENTRY;
857		c->flags |= CLIENT_REDRAWSTATUS;
858		return (0);
859	}
860
861	switch (key) {
862	case 'A':
863	case '$':
864		*new_key = KEYC_END;
865		return (1);
866	case 'I':
867	case '0':
868	case '^':
869		*new_key = KEYC_HOME;
870		return (1);
871	case 'C':
872	case 'D':
873		*new_key = '\013'; /* C-k */
874		return (1);
875	case KEYC_BSPACE:
876	case 'X':
877		*new_key = KEYC_BSPACE;
878		return (1);
879	case 'b':
880		*new_key = 'b'|KEYC_META;
881		return (1);
882	case 'B':
883		*new_key = 'B'|KEYC_VI;
884		return (1);
885	case 'd':
886		*new_key = '\025';
887		return (1);
888	case 'e':
889		*new_key = 'e'|KEYC_VI;
890		return (1);
891	case 'E':
892		*new_key = 'E'|KEYC_VI;
893		return (1);
894	case 'w':
895		*new_key = 'w'|KEYC_VI;
896		return (1);
897	case 'W':
898		*new_key = 'W'|KEYC_VI;
899		return (1);
900	case 'p':
901		*new_key = '\031'; /* C-y */
902		return (1);
903	case 'q':
904		*new_key = '\003'; /* C-c */
905		return (1);
906	case 's':
907	case KEYC_DC:
908	case 'x':
909		*new_key = KEYC_DC;
910		return (1);
911	case KEYC_DOWN:
912	case 'j':
913		*new_key = KEYC_DOWN;
914		return (1);
915	case KEYC_LEFT:
916	case 'h':
917		*new_key = KEYC_LEFT;
918		return (1);
919	case 'a':
920	case KEYC_RIGHT:
921	case 'l':
922		*new_key = KEYC_RIGHT;
923		return (1);
924	case KEYC_UP:
925	case 'k':
926		*new_key = KEYC_UP;
927		return (1);
928	case '\010' /* C-h */:
929	case '\003' /* C-c */:
930	case '\n':
931	case '\r':
932		return (1);
933	}
934	return (0);
935}
936
937/* Paste into prompt. */
938static int
939status_prompt_paste(struct client *c)
940{
941	struct paste_buffer	*pb;
942	const char		*bufdata;
943	size_t			 size, n, bufsize;
944	u_int			 i;
945	struct utf8_data	*ud, *udp;
946	enum utf8_state		 more;
947
948	size = utf8_strlen(c->prompt_buffer);
949	if (c->prompt_saved != NULL) {
950		ud = c->prompt_saved;
951		n = utf8_strlen(c->prompt_saved);
952	} else {
953		if ((pb = paste_get_top(NULL)) == NULL)
954			return (0);
955		bufdata = paste_buffer_data(pb, &bufsize);
956		ud = xreallocarray(NULL, bufsize + 1, sizeof *ud);
957		udp = ud;
958		for (i = 0; i != bufsize; /* nothing */) {
959			more = utf8_open(udp, bufdata[i]);
960			if (more == UTF8_MORE) {
961				while (++i != bufsize && more == UTF8_MORE)
962					more = utf8_append(udp, bufdata[i]);
963				if (more == UTF8_DONE) {
964					udp++;
965					continue;
966				}
967				i -= udp->have;
968			}
969			if (bufdata[i] <= 31 || bufdata[i] >= 127)
970				break;
971			utf8_set(udp, bufdata[i]);
972			udp++;
973			i++;
974		}
975		udp->size = 0;
976		n = udp - ud;
977	}
978	if (n == 0)
979		return (0);
980
981	c->prompt_buffer = xreallocarray(c->prompt_buffer, size + n + 1,
982	    sizeof *c->prompt_buffer);
983	if (c->prompt_index == size) {
984		memcpy(c->prompt_buffer + c->prompt_index, ud,
985		    n * sizeof *c->prompt_buffer);
986		c->prompt_index += n;
987		c->prompt_buffer[c->prompt_index].size = 0;
988	} else {
989		memmove(c->prompt_buffer + c->prompt_index + n,
990		    c->prompt_buffer + c->prompt_index,
991		    (size + 1 - c->prompt_index) * sizeof *c->prompt_buffer);
992		memcpy(c->prompt_buffer + c->prompt_index, ud,
993		    n * sizeof *c->prompt_buffer);
994		c->prompt_index += n;
995	}
996
997	if (ud != c->prompt_saved)
998		free(ud);
999	return (1);
1000}
1001
1002/* Finish completion. */
1003static int
1004status_prompt_replace_complete(struct client *c, const char *s)
1005{
1006	char			 word[64], *allocated = NULL;
1007	size_t			 size, n, off, idx, used;
1008	struct utf8_data	*first, *last, *ud;
1009
1010	/* Work out where the cursor currently is. */
1011	idx = c->prompt_index;
1012	if (idx != 0)
1013		idx--;
1014	size = utf8_strlen(c->prompt_buffer);
1015
1016	/* Find the word we are in. */
1017	first = &c->prompt_buffer[idx];
1018	while (first > c->prompt_buffer && !status_prompt_space(first))
1019		first--;
1020	while (first->size != 0 && status_prompt_space(first))
1021		first++;
1022	last = &c->prompt_buffer[idx];
1023	while (last->size != 0 && !status_prompt_space(last))
1024		last++;
1025	while (last > c->prompt_buffer && status_prompt_space(last))
1026		last--;
1027	if (last->size != 0)
1028		last++;
1029	if (last < first)
1030		return (0);
1031	if (s == NULL) {
1032		used = 0;
1033		for (ud = first; ud < last; ud++) {
1034			if (used + ud->size >= sizeof word)
1035				break;
1036			memcpy(word + used, ud->data, ud->size);
1037			used += ud->size;
1038		}
1039		if (ud != last)
1040			return (0);
1041		word[used] = '\0';
1042	}
1043
1044	/* Try to complete it. */
1045	if (s == NULL) {
1046		allocated = status_prompt_complete(c, word,
1047		    first - c->prompt_buffer);
1048		if (allocated == NULL)
1049			return (0);
1050		s = allocated;
1051	}
1052
1053	/* Trim out word. */
1054	n = size - (last - c->prompt_buffer) + 1; /* with \0 */
1055	memmove(first, last, n * sizeof *c->prompt_buffer);
1056	size -= last - first;
1057
1058	/* Insert the new word. */
1059	size += strlen(s);
1060	off = first - c->prompt_buffer;
1061	c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 1,
1062	    sizeof *c->prompt_buffer);
1063	first = c->prompt_buffer + off;
1064	memmove(first + strlen(s), first, n * sizeof *c->prompt_buffer);
1065	for (idx = 0; idx < strlen(s); idx++)
1066		utf8_set(&first[idx], s[idx]);
1067	c->prompt_index = (first - c->prompt_buffer) + strlen(s);
1068
1069	free(allocated);
1070	return (1);
1071}
1072
1073/* Prompt forward to the next beginning of a word. */
1074static void
1075status_prompt_forward_word(struct client *c, size_t size, int vi,
1076    const char *separators)
1077{
1078	size_t		 idx = c->prompt_index;
1079	int		 word_is_separators;
1080
1081	/* In emacs mode, skip until the first non-whitespace character. */
1082	if (!vi)
1083		while (idx != size &&
1084		    status_prompt_space(&c->prompt_buffer[idx]))
1085			idx++;
1086
1087	/* Can't move forward if we're already at the end. */
1088	if (idx == size) {
1089		c->prompt_index = idx;
1090		return;
1091	}
1092
1093	/* Determine the current character class (separators or not). */
1094	word_is_separators = status_prompt_in_list(separators,
1095	    &c->prompt_buffer[idx]) &&
1096	    !status_prompt_space(&c->prompt_buffer[idx]);
1097
1098	/* Skip ahead until the first space or opposite character class. */
1099	do {
1100		idx++;
1101		if (status_prompt_space(&c->prompt_buffer[idx])) {
1102			/* In vi mode, go to the start of the next word. */
1103			if (vi)
1104				while (idx != size &&
1105				    status_prompt_space(&c->prompt_buffer[idx]))
1106					idx++;
1107			break;
1108		}
1109	} while (idx != size && word_is_separators == status_prompt_in_list(
1110	    separators, &c->prompt_buffer[idx]));
1111
1112	c->prompt_index = idx;
1113}
1114
1115/* Prompt forward to the next end of a word. */
1116static void
1117status_prompt_end_word(struct client *c, size_t size, const char *separators)
1118{
1119	size_t		 idx = c->prompt_index;
1120	int		 word_is_separators;
1121
1122	/* Can't move forward if we're already at the end. */
1123	if (idx == size)
1124		return;
1125
1126	/* Find the next word. */
1127	do {
1128		idx++;
1129		if (idx == size) {
1130			c->prompt_index = idx;
1131			return;
1132		}
1133	} while (status_prompt_space(&c->prompt_buffer[idx]));
1134
1135	/* Determine the character class (separators or not). */
1136	word_is_separators = status_prompt_in_list(separators,
1137	    &c->prompt_buffer[idx]);
1138
1139	/* Skip ahead until the next space or opposite character class. */
1140	do {
1141		idx++;
1142		if (idx == size)
1143			break;
1144	} while (!status_prompt_space(&c->prompt_buffer[idx]) &&
1145	    word_is_separators == status_prompt_in_list(separators,
1146	    &c->prompt_buffer[idx]));
1147
1148	/* Back up to the previous character to stop at the end of the word. */
1149	c->prompt_index = idx - 1;
1150}
1151
1152/* Prompt backward to the previous beginning of a word. */
1153static void
1154status_prompt_backward_word(struct client *c, const char *separators)
1155{
1156	size_t	idx = c->prompt_index;
1157	int	word_is_separators;
1158
1159	/* Find non-whitespace. */
1160	while (idx != 0) {
1161		--idx;
1162		if (!status_prompt_space(&c->prompt_buffer[idx]))
1163			break;
1164	}
1165	word_is_separators = status_prompt_in_list(separators,
1166	    &c->prompt_buffer[idx]);
1167
1168	/* Find the character before the beginning of the word. */
1169	while (idx != 0) {
1170		--idx;
1171		if (status_prompt_space(&c->prompt_buffer[idx]) ||
1172		    word_is_separators != status_prompt_in_list(separators,
1173		    &c->prompt_buffer[idx])) {
1174			/* Go back to the word. */
1175			idx++;
1176			break;
1177		}
1178	}
1179	c->prompt_index = idx;
1180}
1181
1182/* Handle keys in prompt. */
1183int
1184status_prompt_key(struct client *c, key_code key)
1185{
1186	struct options		*oo = c->session->options;
1187	char			*s, *cp, prefix = '=';
1188	const char		*histstr, *separators = NULL, *keystring;
1189	size_t			 size, idx;
1190	struct utf8_data	 tmp;
1191	int			 keys, word_is_separators;
1192
1193	if (c->prompt_flags & PROMPT_KEY) {
1194		keystring = key_string_lookup_key(key, 0);
1195		c->prompt_inputcb(c, c->prompt_data, keystring, 1);
1196		status_prompt_clear(c);
1197		return (0);
1198	}
1199	size = utf8_strlen(c->prompt_buffer);
1200
1201	if (c->prompt_flags & PROMPT_NUMERIC) {
1202		if (key >= '0' && key <= '9')
1203			goto append_key;
1204		s = utf8_tocstr(c->prompt_buffer);
1205		c->prompt_inputcb(c, c->prompt_data, s, 1);
1206		status_prompt_clear(c);
1207		free(s);
1208		return (1);
1209	}
1210	key &= ~KEYC_MASK_FLAGS;
1211
1212	keys = options_get_number(c->session->options, "status-keys");
1213	if (keys == MODEKEY_VI) {
1214		switch (status_prompt_translate_key(c, key, &key)) {
1215		case 1:
1216			goto process_key;
1217		case 2:
1218			goto append_key;
1219		default:
1220			return (0);
1221		}
1222	}
1223
1224process_key:
1225	switch (key) {
1226	case KEYC_LEFT:
1227	case '\002': /* C-b */
1228		if (c->prompt_index > 0) {
1229			c->prompt_index--;
1230			break;
1231		}
1232		break;
1233	case KEYC_RIGHT:
1234	case '\006': /* C-f */
1235		if (c->prompt_index < size) {
1236			c->prompt_index++;
1237			break;
1238		}
1239		break;
1240	case KEYC_HOME:
1241	case '\001': /* C-a */
1242		if (c->prompt_index != 0) {
1243			c->prompt_index = 0;
1244			break;
1245		}
1246		break;
1247	case KEYC_END:
1248	case '\005': /* C-e */
1249		if (c->prompt_index != size) {
1250			c->prompt_index = size;
1251			break;
1252		}
1253		break;
1254	case '\011': /* Tab */
1255		if (status_prompt_replace_complete(c, NULL))
1256			goto changed;
1257		break;
1258	case KEYC_BSPACE:
1259	case '\010': /* C-h */
1260		if (c->prompt_index != 0) {
1261			if (c->prompt_index == size)
1262				c->prompt_buffer[--c->prompt_index].size = 0;
1263			else {
1264				memmove(c->prompt_buffer + c->prompt_index - 1,
1265				    c->prompt_buffer + c->prompt_index,
1266				    (size + 1 - c->prompt_index) *
1267				    sizeof *c->prompt_buffer);
1268				c->prompt_index--;
1269			}
1270			goto changed;
1271		}
1272		break;
1273	case KEYC_DC:
1274	case '\004': /* C-d */
1275		if (c->prompt_index != size) {
1276			memmove(c->prompt_buffer + c->prompt_index,
1277			    c->prompt_buffer + c->prompt_index + 1,
1278			    (size + 1 - c->prompt_index) *
1279			    sizeof *c->prompt_buffer);
1280			goto changed;
1281		}
1282		break;
1283	case '\025': /* C-u */
1284		c->prompt_buffer[0].size = 0;
1285		c->prompt_index = 0;
1286		goto changed;
1287	case '\013': /* C-k */
1288		if (c->prompt_index < size) {
1289			c->prompt_buffer[c->prompt_index].size = 0;
1290			goto changed;
1291		}
1292		break;
1293	case '\027': /* C-w */
1294		separators = options_get_string(oo, "word-separators");
1295		idx = c->prompt_index;
1296
1297		/* Find non-whitespace. */
1298		while (idx != 0) {
1299			idx--;
1300			if (!status_prompt_space(&c->prompt_buffer[idx]))
1301				break;
1302		}
1303		word_is_separators = status_prompt_in_list(separators,
1304		    &c->prompt_buffer[idx]);
1305
1306		/* Find the character before the beginning of the word. */
1307		while (idx != 0) {
1308			idx--;
1309			if (status_prompt_space(&c->prompt_buffer[idx]) ||
1310			    word_is_separators != status_prompt_in_list(
1311			    separators, &c->prompt_buffer[idx])) {
1312				/* Go back to the word. */
1313				idx++;
1314				break;
1315			}
1316		}
1317
1318		free(c->prompt_saved);
1319		c->prompt_saved = xcalloc(sizeof *c->prompt_buffer,
1320		    (c->prompt_index - idx) + 1);
1321		memcpy(c->prompt_saved, c->prompt_buffer + idx,
1322		    (c->prompt_index - idx) * sizeof *c->prompt_buffer);
1323
1324		memmove(c->prompt_buffer + idx,
1325		    c->prompt_buffer + c->prompt_index,
1326		    (size + 1 - c->prompt_index) *
1327		    sizeof *c->prompt_buffer);
1328		memset(c->prompt_buffer + size - (c->prompt_index - idx),
1329		    '\0', (c->prompt_index - idx) * sizeof *c->prompt_buffer);
1330		c->prompt_index = idx;
1331
1332		goto changed;
1333	case KEYC_RIGHT|KEYC_CTRL:
1334	case 'f'|KEYC_META:
1335		separators = options_get_string(oo, "word-separators");
1336		status_prompt_forward_word(c, size, 0, separators);
1337		goto changed;
1338	case 'E'|KEYC_VI:
1339		status_prompt_end_word(c, size, "");
1340		goto changed;
1341	case 'e'|KEYC_VI:
1342		separators = options_get_string(oo, "word-separators");
1343		status_prompt_end_word(c, size, separators);
1344		goto changed;
1345	case 'W'|KEYC_VI:
1346		status_prompt_forward_word(c, size, 1, "");
1347		goto changed;
1348	case 'w'|KEYC_VI:
1349		separators = options_get_string(oo, "word-separators");
1350		status_prompt_forward_word(c, size, 1, separators);
1351		goto changed;
1352	case 'B'|KEYC_VI:
1353		status_prompt_backward_word(c, "");
1354		goto changed;
1355	case KEYC_LEFT|KEYC_CTRL:
1356	case 'b'|KEYC_META:
1357		separators = options_get_string(oo, "word-separators");
1358		status_prompt_backward_word(c, separators);
1359		goto changed;
1360	case KEYC_UP:
1361	case '\020': /* C-p */
1362		histstr = status_prompt_up_history(c->prompt_hindex,
1363		    c->prompt_type);
1364		if (histstr == NULL)
1365			break;
1366		free(c->prompt_buffer);
1367		c->prompt_buffer = utf8_fromcstr(histstr);
1368		c->prompt_index = utf8_strlen(c->prompt_buffer);
1369		goto changed;
1370	case KEYC_DOWN:
1371	case '\016': /* C-n */
1372		histstr = status_prompt_down_history(c->prompt_hindex,
1373		    c->prompt_type);
1374		if (histstr == NULL)
1375			break;
1376		free(c->prompt_buffer);
1377		c->prompt_buffer = utf8_fromcstr(histstr);
1378		c->prompt_index = utf8_strlen(c->prompt_buffer);
1379		goto changed;
1380	case '\031': /* C-y */
1381		if (status_prompt_paste(c))
1382			goto changed;
1383		break;
1384	case '\024': /* C-t */
1385		idx = c->prompt_index;
1386		if (idx < size)
1387			idx++;
1388		if (idx >= 2) {
1389			utf8_copy(&tmp, &c->prompt_buffer[idx - 2]);
1390			utf8_copy(&c->prompt_buffer[idx - 2],
1391			    &c->prompt_buffer[idx - 1]);
1392			utf8_copy(&c->prompt_buffer[idx - 1], &tmp);
1393			c->prompt_index = idx;
1394			goto changed;
1395		}
1396		break;
1397	case '\r':
1398	case '\n':
1399		s = utf8_tocstr(c->prompt_buffer);
1400		if (*s != '\0')
1401			status_prompt_add_history(s, c->prompt_type);
1402		if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0)
1403			status_prompt_clear(c);
1404		free(s);
1405		break;
1406	case '\033': /* Escape */
1407	case '\003': /* C-c */
1408	case '\007': /* C-g */
1409		if (c->prompt_inputcb(c, c->prompt_data, NULL, 1) == 0)
1410			status_prompt_clear(c);
1411		break;
1412	case '\022': /* C-r */
1413		if (~c->prompt_flags & PROMPT_INCREMENTAL)
1414			break;
1415		if (c->prompt_buffer[0].size == 0) {
1416			prefix = '=';
1417			free(c->prompt_buffer);
1418			c->prompt_buffer = utf8_fromcstr(c->prompt_last);
1419			c->prompt_index = utf8_strlen(c->prompt_buffer);
1420		} else
1421			prefix = '-';
1422		goto changed;
1423	case '\023': /* C-s */
1424		if (~c->prompt_flags & PROMPT_INCREMENTAL)
1425			break;
1426		if (c->prompt_buffer[0].size == 0) {
1427			prefix = '=';
1428			free(c->prompt_buffer);
1429			c->prompt_buffer = utf8_fromcstr(c->prompt_last);
1430			c->prompt_index = utf8_strlen(c->prompt_buffer);
1431		} else
1432			prefix = '+';
1433		goto changed;
1434	default:
1435		goto append_key;
1436	}
1437
1438	c->flags |= CLIENT_REDRAWSTATUS;
1439	return (0);
1440
1441append_key:
1442	if (key <= 0x1f || (key >= KEYC_BASE && key < KEYC_BASE_END))
1443		return (0);
1444	if (key <= 0x7f)
1445		utf8_set(&tmp, key);
1446	else if (KEYC_IS_UNICODE(key))
1447		utf8_to_data(key, &tmp);
1448	else
1449		return (0);
1450
1451	c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 2,
1452	    sizeof *c->prompt_buffer);
1453
1454	if (c->prompt_index == size) {
1455		utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp);
1456		c->prompt_index++;
1457		c->prompt_buffer[c->prompt_index].size = 0;
1458	} else {
1459		memmove(c->prompt_buffer + c->prompt_index + 1,
1460		    c->prompt_buffer + c->prompt_index,
1461		    (size + 1 - c->prompt_index) *
1462		    sizeof *c->prompt_buffer);
1463		utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp);
1464		c->prompt_index++;
1465	}
1466
1467	if (c->prompt_flags & PROMPT_SINGLE) {
1468		if (utf8_strlen(c->prompt_buffer) != 1)
1469			status_prompt_clear(c);
1470		else {
1471			s = utf8_tocstr(c->prompt_buffer);
1472			if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0)
1473				status_prompt_clear(c);
1474			free(s);
1475		}
1476	}
1477
1478changed:
1479	c->flags |= CLIENT_REDRAWSTATUS;
1480	if (c->prompt_flags & PROMPT_INCREMENTAL) {
1481		s = utf8_tocstr(c->prompt_buffer);
1482		xasprintf(&cp, "%c%s", prefix, s);
1483		c->prompt_inputcb(c, c->prompt_data, cp, 0);
1484		free(cp);
1485		free(s);
1486	}
1487	return (0);
1488}
1489
1490/* Get previous line from the history. */
1491static const char *
1492status_prompt_up_history(u_int *idx, u_int type)
1493{
1494	/*
1495	 * History runs from 0 to size - 1. Index is from 0 to size. Zero is
1496	 * empty.
1497	 */
1498
1499	if (status_prompt_hsize[type] == 0 ||
1500	    idx[type] == status_prompt_hsize[type])
1501		return (NULL);
1502	idx[type]++;
1503	return (status_prompt_hlist[type][status_prompt_hsize[type] - idx[type]]);
1504}
1505
1506/* Get next line from the history. */
1507static const char *
1508status_prompt_down_history(u_int *idx, u_int type)
1509{
1510	if (status_prompt_hsize[type] == 0 || idx[type] == 0)
1511		return ("");
1512	idx[type]--;
1513	if (idx[type] == 0)
1514		return ("");
1515	return (status_prompt_hlist[type][status_prompt_hsize[type] - idx[type]]);
1516}
1517
1518/* Add line to the history. */
1519static void
1520status_prompt_add_history(const char *line, u_int type)
1521{
1522	u_int	i, oldsize, newsize, freecount, hlimit, new = 1;
1523	size_t	movesize;
1524
1525	oldsize = status_prompt_hsize[type];
1526	if (oldsize > 0 &&
1527	    strcmp(status_prompt_hlist[type][oldsize - 1], line) == 0)
1528		new = 0;
1529
1530	hlimit = options_get_number(global_options, "prompt-history-limit");
1531	if (hlimit > oldsize) {
1532		if (new == 0)
1533			return;
1534		newsize = oldsize + new;
1535	} else {
1536		newsize = hlimit;
1537		freecount = oldsize + new - newsize;
1538		if (freecount > oldsize)
1539			freecount = oldsize;
1540		if (freecount == 0)
1541			return;
1542		for (i = 0; i < freecount; i++)
1543			free(status_prompt_hlist[type][i]);
1544		movesize = (oldsize - freecount) *
1545		    sizeof *status_prompt_hlist[type];
1546		if (movesize > 0) {
1547			memmove(&status_prompt_hlist[type][0],
1548			    &status_prompt_hlist[type][freecount], movesize);
1549		}
1550	}
1551
1552	if (newsize == 0) {
1553		free(status_prompt_hlist[type]);
1554		status_prompt_hlist[type] = NULL;
1555	} else if (newsize != oldsize) {
1556		status_prompt_hlist[type] =
1557		    xreallocarray(status_prompt_hlist[type], newsize,
1558			sizeof *status_prompt_hlist[type]);
1559	}
1560
1561	if (new == 1 && newsize > 0)
1562		status_prompt_hlist[type][newsize - 1] = xstrdup(line);
1563	status_prompt_hsize[type] = newsize;
1564}
1565
1566/* Build completion list. */
1567static char **
1568status_prompt_complete_list(u_int *size, const char *s, int at_start)
1569{
1570	char					**list = NULL;
1571	const char				**layout, *value, *cp;
1572	const struct cmd_entry			**cmdent;
1573	const struct options_table_entry	 *oe;
1574	size_t					  slen = strlen(s), valuelen;
1575	struct options_entry			 *o;
1576	struct options_array_item		 *a;
1577	const char				 *layouts[] = {
1578		"even-horizontal", "even-vertical", "main-horizontal",
1579		"main-vertical", "tiled", NULL
1580	};
1581
1582	*size = 0;
1583	for (cmdent = cmd_table; *cmdent != NULL; cmdent++) {
1584		if (strncmp((*cmdent)->name, s, slen) == 0) {
1585			list = xreallocarray(list, (*size) + 1, sizeof *list);
1586			list[(*size)++] = xstrdup((*cmdent)->name);
1587		}
1588		if ((*cmdent)->alias != NULL &&
1589		    strncmp((*cmdent)->alias, s, slen) == 0) {
1590			list = xreallocarray(list, (*size) + 1, sizeof *list);
1591			list[(*size)++] = xstrdup((*cmdent)->alias);
1592		}
1593	}
1594	o = options_get_only(global_options, "command-alias");
1595	if (o != NULL) {
1596		a = options_array_first(o);
1597		while (a != NULL) {
1598			value = options_array_item_value(a)->string;
1599			if ((cp = strchr(value, '=')) == NULL)
1600				goto next;
1601			valuelen = cp - value;
1602			if (slen > valuelen || strncmp(value, s, slen) != 0)
1603				goto next;
1604
1605			list = xreallocarray(list, (*size) + 1, sizeof *list);
1606			list[(*size)++] = xstrndup(value, valuelen);
1607
1608		next:
1609			a = options_array_next(a);
1610		}
1611	}
1612	if (at_start)
1613		return (list);
1614
1615	for (oe = options_table; oe->name != NULL; oe++) {
1616		if (strncmp(oe->name, s, slen) == 0) {
1617			list = xreallocarray(list, (*size) + 1, sizeof *list);
1618			list[(*size)++] = xstrdup(oe->name);
1619		}
1620	}
1621	for (layout = layouts; *layout != NULL; layout++) {
1622		if (strncmp(*layout, s, slen) == 0) {
1623			list = xreallocarray(list, (*size) + 1, sizeof *list);
1624			list[(*size)++] = xstrdup(*layout);
1625		}
1626	}
1627	return (list);
1628}
1629
1630/* Find longest prefix. */
1631static char *
1632status_prompt_complete_prefix(char **list, u_int size)
1633{
1634	char	 *out;
1635	u_int	  i;
1636	size_t	  j;
1637
1638	if (list == NULL || size == 0)
1639		return (NULL);
1640	out = xstrdup(list[0]);
1641	for (i = 1; i < size; i++) {
1642		j = strlen(list[i]);
1643		if (j > strlen(out))
1644			j = strlen(out);
1645		for (; j > 0; j--) {
1646			if (out[j - 1] != list[i][j - 1])
1647				out[j - 1] = '\0';
1648		}
1649	}
1650	return (out);
1651}
1652
1653/* Complete word menu callback. */
1654static void
1655status_prompt_menu_callback(__unused struct menu *menu, u_int idx, key_code key,
1656    void *data)
1657{
1658	struct status_prompt_menu	*spm = data;
1659	struct client			*c = spm->c;
1660	u_int				 i;
1661	char				*s;
1662
1663	if (key != KEYC_NONE) {
1664		idx += spm->start;
1665		if (spm->flag == '\0')
1666			s = xstrdup(spm->list[idx]);
1667		else
1668			xasprintf(&s, "-%c%s", spm->flag, spm->list[idx]);
1669		if (c->prompt_type == PROMPT_TYPE_WINDOW_TARGET) {
1670			free(c->prompt_buffer);
1671			c->prompt_buffer = utf8_fromcstr(s);
1672			c->prompt_index = utf8_strlen(c->prompt_buffer);
1673			c->flags |= CLIENT_REDRAWSTATUS;
1674		} else if (status_prompt_replace_complete(c, s))
1675			c->flags |= CLIENT_REDRAWSTATUS;
1676		free(s);
1677	}
1678
1679	for (i = 0; i < spm->size; i++)
1680		free(spm->list[i]);
1681	free(spm->list);
1682}
1683
1684/* Show complete word menu. */
1685static int
1686status_prompt_complete_list_menu(struct client *c, char **list, u_int size,
1687    u_int offset, char flag)
1688{
1689	struct menu			*menu;
1690	struct menu_item		 item;
1691	struct status_prompt_menu	*spm;
1692	u_int				 lines = status_line_size(c), height, i;
1693	u_int				 py;
1694
1695	if (size <= 1)
1696		return (0);
1697	if (c->tty.sy - lines < 3)
1698		return (0);
1699
1700	spm = xmalloc(sizeof *spm);
1701	spm->c = c;
1702	spm->size = size;
1703	spm->list = list;
1704	spm->flag = flag;
1705
1706	height = c->tty.sy - lines - 2;
1707	if (height > 10)
1708		height = 10;
1709	if (height > size)
1710		height = size;
1711	spm->start = size - height;
1712
1713	menu = menu_create("");
1714	for (i = spm->start; i < size; i++) {
1715		item.name = list[i];
1716		item.key = '0' + (i - spm->start);
1717		item.command = NULL;
1718		menu_add_item(menu, &item, NULL, NULL, NULL);
1719	}
1720
1721	if (options_get_number(c->session->options, "status-position") == 0)
1722		py = lines;
1723	else
1724		py = c->tty.sy - 3 - height;
1725	offset += utf8_cstrwidth(c->prompt_string);
1726	if (offset > 2)
1727		offset -= 2;
1728	else
1729		offset = 0;
1730
1731	if (menu_display(menu, MENU_NOMOUSE|MENU_TAB, NULL, offset,
1732	    py, c, NULL, status_prompt_menu_callback, spm) != 0) {
1733		menu_free(menu);
1734		free(spm);
1735		return (0);
1736	}
1737	return (1);
1738}
1739
1740/* Show complete word menu. */
1741static char *
1742status_prompt_complete_window_menu(struct client *c, struct session *s,
1743    const char *word, u_int offset, char flag)
1744{
1745	struct menu			 *menu;
1746	struct menu_item		  item;
1747	struct status_prompt_menu	 *spm;
1748	struct winlink			 *wl;
1749	char				**list = NULL, *tmp;
1750	u_int				  lines = status_line_size(c), height;
1751	u_int				  py, size = 0;
1752
1753	if (c->tty.sy - lines < 3)
1754		return (NULL);
1755
1756	spm = xmalloc(sizeof *spm);
1757	spm->c = c;
1758	spm->flag = flag;
1759
1760	height = c->tty.sy - lines - 2;
1761	if (height > 10)
1762		height = 10;
1763	spm->start = 0;
1764
1765	menu = menu_create("");
1766	RB_FOREACH(wl, winlinks, &s->windows) {
1767		if (word != NULL && *word != '\0') {
1768			xasprintf(&tmp, "%d", wl->idx);
1769			if (strncmp(tmp, word, strlen(word)) != 0) {
1770				free(tmp);
1771				continue;
1772			}
1773			free(tmp);
1774		}
1775
1776		list = xreallocarray(list, size + 1, sizeof *list);
1777		if (c->prompt_type == PROMPT_TYPE_WINDOW_TARGET) {
1778			xasprintf(&tmp, "%d (%s)", wl->idx, wl->window->name);
1779			xasprintf(&list[size++], "%d", wl->idx);
1780		} else {
1781			xasprintf(&tmp, "%s:%d (%s)", s->name, wl->idx,
1782			    wl->window->name);
1783			xasprintf(&list[size++], "%s:%d", s->name, wl->idx);
1784		}
1785		item.name = tmp;
1786		item.key = '0' + size - 1;
1787		item.command = NULL;
1788		menu_add_item(menu, &item, NULL, NULL, NULL);
1789		free(tmp);
1790
1791		if (size == height)
1792			break;
1793	}
1794	if (size == 0) {
1795		menu_free(menu);
1796		return (NULL);
1797	}
1798	if (size == 1) {
1799		menu_free(menu);
1800		if (flag != '\0') {
1801			xasprintf(&tmp, "-%c%s", flag, list[0]);
1802			free(list[0]);
1803		} else
1804			tmp = list[0];
1805		free(list);
1806		return (tmp);
1807	}
1808	if (height > size)
1809		height = size;
1810
1811	spm->size = size;
1812	spm->list = list;
1813
1814	if (options_get_number(c->session->options, "status-position") == 0)
1815		py = lines;
1816	else
1817		py = c->tty.sy - 3 - height;
1818	offset += utf8_cstrwidth(c->prompt_string);
1819	if (offset > 2)
1820		offset -= 2;
1821	else
1822		offset = 0;
1823
1824	if (menu_display(menu, MENU_NOMOUSE|MENU_TAB, NULL, offset,
1825	    py, c, NULL, status_prompt_menu_callback, spm) != 0) {
1826		menu_free(menu);
1827		free(spm);
1828		return (NULL);
1829	}
1830	return (NULL);
1831}
1832
1833/* Sort complete list. */
1834static int
1835status_prompt_complete_sort(const void *a, const void *b)
1836{
1837	const char	**aa = (const char **)a, **bb = (const char **)b;
1838
1839	return (strcmp(*aa, *bb));
1840}
1841
1842/* Complete a session. */
1843static char *
1844status_prompt_complete_session(char ***list, u_int *size, const char *s,
1845    char flag)
1846{
1847	struct session	*loop;
1848	char		*out, *tmp, n[11];
1849
1850	RB_FOREACH(loop, sessions, &sessions) {
1851		if (*s == '\0' || strncmp(loop->name, s, strlen(s)) == 0) {
1852			*list = xreallocarray(*list, (*size) + 2,
1853			    sizeof **list);
1854			xasprintf(&(*list)[(*size)++], "%s:", loop->name);
1855		} else if (*s == '$') {
1856			xsnprintf(n, sizeof n, "%u", loop->id);
1857			if (s[1] == '\0' ||
1858			    strncmp(n, s + 1, strlen(s) - 1) == 0) {
1859				*list = xreallocarray(*list, (*size) + 2,
1860				    sizeof **list);
1861				xasprintf(&(*list)[(*size)++], "$%s:", n);
1862			}
1863		}
1864	}
1865	out = status_prompt_complete_prefix(*list, *size);
1866	if (out != NULL && flag != '\0') {
1867		xasprintf(&tmp, "-%c%s", flag, out);
1868		free(out);
1869		out = tmp;
1870	}
1871	return (out);
1872}
1873
1874/* Complete word. */
1875static char *
1876status_prompt_complete(struct client *c, const char *word, u_int offset)
1877{
1878	struct session	 *session;
1879	const char	 *s, *colon;
1880	char		**list = NULL, *copy = NULL, *out = NULL;
1881	char		  flag = '\0';
1882	u_int		  size = 0, i;
1883
1884	if (*word == '\0' &&
1885	    c->prompt_type != PROMPT_TYPE_TARGET &&
1886	    c->prompt_type != PROMPT_TYPE_WINDOW_TARGET)
1887		return (NULL);
1888
1889	if (c->prompt_type != PROMPT_TYPE_TARGET &&
1890	    c->prompt_type != PROMPT_TYPE_WINDOW_TARGET &&
1891	    strncmp(word, "-t", 2) != 0 &&
1892	    strncmp(word, "-s", 2) != 0) {
1893		list = status_prompt_complete_list(&size, word, offset == 0);
1894		if (size == 0)
1895			out = NULL;
1896		else if (size == 1)
1897			xasprintf(&out, "%s ", list[0]);
1898		else
1899			out = status_prompt_complete_prefix(list, size);
1900		goto found;
1901	}
1902
1903	if (c->prompt_type == PROMPT_TYPE_TARGET ||
1904	    c->prompt_type == PROMPT_TYPE_WINDOW_TARGET) {
1905		s = word;
1906		flag = '\0';
1907	} else {
1908		s = word + 2;
1909		flag = word[1];
1910		offset += 2;
1911	}
1912
1913	/* If this is a window completion, open the window menu. */
1914	if (c->prompt_type == PROMPT_TYPE_WINDOW_TARGET) {
1915		out = status_prompt_complete_window_menu(c, c->session, s,
1916		    offset, '\0');
1917		goto found;
1918	}
1919	colon = strchr(s, ':');
1920
1921	/* If there is no colon, complete as a session. */
1922	if (colon == NULL) {
1923		out = status_prompt_complete_session(&list, &size, s, flag);
1924		goto found;
1925	}
1926
1927	/* If there is a colon but no period, find session and show a menu. */
1928	if (strchr(colon + 1, '.') == NULL) {
1929		if (*s == ':')
1930			session = c->session;
1931		else {
1932			copy = xstrdup(s);
1933			*strchr(copy, ':') = '\0';
1934			session = session_find(copy);
1935			free(copy);
1936			if (session == NULL)
1937				goto found;
1938		}
1939		out = status_prompt_complete_window_menu(c, session, colon + 1,
1940		    offset, flag);
1941		if (out == NULL)
1942			return (NULL);
1943	}
1944
1945found:
1946	if (size != 0) {
1947		qsort(list, size, sizeof *list, status_prompt_complete_sort);
1948		for (i = 0; i < size; i++)
1949			log_debug("complete %u: %s", i, list[i]);
1950	}
1951
1952	if (out != NULL && strcmp(word, out) == 0) {
1953		free(out);
1954		out = NULL;
1955	}
1956	if (out != NULL ||
1957	    !status_prompt_complete_list_menu(c, list, size, offset, flag)) {
1958		for (i = 0; i < size; i++)
1959			free(list[i]);
1960		free(list);
1961	}
1962	return (out);
1963}
1964
1965/* Return the type of the prompt as an enum. */
1966enum prompt_type
1967status_prompt_type(const char *type)
1968{
1969	u_int	i;
1970
1971	for (i = 0; i < PROMPT_NTYPES; i++) {
1972		if (strcmp(type, status_prompt_type_string(i)) == 0)
1973			return (i);
1974	}
1975	return (PROMPT_TYPE_INVALID);
1976}
1977
1978/* Accessor for prompt_type_strings. */
1979const char *
1980status_prompt_type_string(u_int type)
1981{
1982	if (type >= PROMPT_NTYPES)
1983		return ("invalid");
1984	return (prompt_type_strings[type]);
1985}
1986