xref: /openbsd-src/usr.bin/tmux/status.c (revision 0460263c2ade1daba2a257e456ad8628a1f040e2)
1*0460263cSnicm /* $OpenBSD: status.c,v 1.250 2024/10/28 08:11:59 nicm Exp $ */
2311827fbSnicm 
3311827fbSnicm /*
498ca8272Snicm  * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
5311827fbSnicm  *
6311827fbSnicm  * Permission to use, copy, modify, and distribute this software for any
7311827fbSnicm  * purpose with or without fee is hereby granted, provided that the above
8311827fbSnicm  * copyright notice and this permission notice appear in all copies.
9311827fbSnicm  *
10311827fbSnicm  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11311827fbSnicm  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12311827fbSnicm  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13311827fbSnicm  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14311827fbSnicm  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15311827fbSnicm  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16311827fbSnicm  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17311827fbSnicm  */
18311827fbSnicm 
19311827fbSnicm #include <sys/types.h>
20311827fbSnicm #include <sys/time.h>
21311827fbSnicm 
22311827fbSnicm #include <errno.h>
23311827fbSnicm #include <limits.h>
24311827fbSnicm #include <stdarg.h>
25311827fbSnicm #include <stdlib.h>
26311827fbSnicm #include <string.h>
27311827fbSnicm #include <time.h>
28311827fbSnicm #include <unistd.h>
29311827fbSnicm 
30311827fbSnicm #include "tmux.h"
31311827fbSnicm 
329883b791Snicm static void	 status_message_callback(int, short, void *);
339883b791Snicm static void	 status_timer_callback(int, short, void *);
34311827fbSnicm 
359883b791Snicm static char	*status_prompt_find_history_file(void);
36bc5a8fc2Snicm static const char *status_prompt_up_history(u_int *, u_int);
37bc5a8fc2Snicm static const char *status_prompt_down_history(u_int *, u_int);
38bc5a8fc2Snicm static void	 status_prompt_add_history(const char *, u_int);
39276a572eSnicm 
40d6317402Snicm static char	*status_prompt_complete(struct client *, const char *, u_int);
4137a3aee2Snicm static char	*status_prompt_complete_window_menu(struct client *,
42e484fb00Snicm 		     struct session *, const char *, u_int, char);
43d6317402Snicm 
44d6317402Snicm struct status_prompt_menu {
45d6317402Snicm 	struct client	 *c;
46d6317402Snicm 	u_int		  start;
47d6317402Snicm 	u_int		  size;
48d6317402Snicm 	char		**list;
49d6317402Snicm 	char		  flag;
50d6317402Snicm };
51179ef399Snicm 
52bc5a8fc2Snicm static const char	*prompt_type_strings[] = {
53bc5a8fc2Snicm 	"command",
54bc5a8fc2Snicm 	"search",
55bc5a8fc2Snicm 	"target",
56bc5a8fc2Snicm 	"window-target"
57bc5a8fc2Snicm };
58bc5a8fc2Snicm 
594ee77f13Snicm /* Status prompt history. */
60bc5a8fc2Snicm char		**status_prompt_hlist[PROMPT_NTYPES];
61bc5a8fc2Snicm u_int		  status_prompt_hsize[PROMPT_NTYPES];
624ee77f13Snicm 
63179ef399Snicm /* Find the history file to load/save from/to. */
649883b791Snicm static char *
65179ef399Snicm status_prompt_find_history_file(void)
66179ef399Snicm {
67179ef399Snicm 	const char	*home, *history_file;
68179ef399Snicm 	char		*path;
69179ef399Snicm 
70d89252e5Snicm 	history_file = options_get_string(global_options, "history-file");
71179ef399Snicm 	if (*history_file == '\0')
72179ef399Snicm 		return (NULL);
73179ef399Snicm 	if (*history_file == '/')
74179ef399Snicm 		return (xstrdup(history_file));
75179ef399Snicm 
76179ef399Snicm 	if (history_file[0] != '~' || history_file[1] != '/')
77179ef399Snicm 		return (NULL);
78179ef399Snicm 	if ((home = find_home()) == NULL)
79179ef399Snicm 		return (NULL);
80179ef399Snicm 	xasprintf(&path, "%s%s", home, history_file + 1);
81179ef399Snicm 	return (path);
82179ef399Snicm }
83179ef399Snicm 
84bc5a8fc2Snicm /* Add loaded history item to the appropriate list. */
85bc5a8fc2Snicm static void
86bc5a8fc2Snicm status_prompt_add_typed_history(char *line)
87bc5a8fc2Snicm {
88bc5a8fc2Snicm 	char			*typestr;
89bc5a8fc2Snicm 	enum prompt_type	 type = PROMPT_TYPE_INVALID;
90bc5a8fc2Snicm 
91bc5a8fc2Snicm 	typestr = strsep(&line, ":");
92bc5a8fc2Snicm 	if (line != NULL)
93bc5a8fc2Snicm 		type = status_prompt_type(typestr);
94bc5a8fc2Snicm 	if (type == PROMPT_TYPE_INVALID) {
95bc5a8fc2Snicm 		/*
96bc5a8fc2Snicm 		 * Invalid types are not expected, but this provides backward
97bc5a8fc2Snicm 		 * compatibility with old history files.
98bc5a8fc2Snicm 		 */
99bc5a8fc2Snicm 		if (line != NULL)
100bc5a8fc2Snicm 			*(--line) = ':';
101bc5a8fc2Snicm 		status_prompt_add_history(typestr, PROMPT_TYPE_COMMAND);
102bc5a8fc2Snicm 	} else
103bc5a8fc2Snicm 		status_prompt_add_history(line, type);
104bc5a8fc2Snicm }
105bc5a8fc2Snicm 
106179ef399Snicm /* Load status prompt history from file. */
107179ef399Snicm void
108179ef399Snicm status_prompt_load_history(void)
109179ef399Snicm {
110179ef399Snicm 	FILE	*f;
111179ef399Snicm 	char	*history_file, *line, *tmp;
112179ef399Snicm 	size_t	 length;
113179ef399Snicm 
114179ef399Snicm 	if ((history_file = status_prompt_find_history_file()) == NULL)
115179ef399Snicm 		return;
116179ef399Snicm 	log_debug("loading history from %s", history_file);
117179ef399Snicm 
118179ef399Snicm 	f = fopen(history_file, "r");
119179ef399Snicm 	if (f == NULL) {
120179ef399Snicm 		log_debug("%s: %s", history_file, strerror(errno));
121179ef399Snicm 		free(history_file);
122179ef399Snicm 		return;
123179ef399Snicm 	}
124179ef399Snicm 	free(history_file);
125179ef399Snicm 
126179ef399Snicm 	for (;;) {
127179ef399Snicm 		if ((line = fgetln(f, &length)) == NULL)
128179ef399Snicm 			break;
129179ef399Snicm 
130179ef399Snicm 		if (length > 0) {
131179ef399Snicm 			if (line[length - 1] == '\n') {
132179ef399Snicm 				line[length - 1] = '\0';
133bc5a8fc2Snicm 				status_prompt_add_typed_history(line);
134179ef399Snicm 			} else {
135179ef399Snicm 				tmp = xmalloc(length + 1);
136179ef399Snicm 				memcpy(tmp, line, length);
137179ef399Snicm 				tmp[length] = '\0';
138bc5a8fc2Snicm 				status_prompt_add_typed_history(tmp);
139179ef399Snicm 				free(tmp);
140179ef399Snicm 			}
141179ef399Snicm 		}
142179ef399Snicm 	}
143179ef399Snicm 	fclose(f);
144179ef399Snicm }
145179ef399Snicm 
146179ef399Snicm /* Save status prompt history to file. */
147179ef399Snicm void
148179ef399Snicm status_prompt_save_history(void)
149179ef399Snicm {
150179ef399Snicm 	FILE	*f;
151bc5a8fc2Snicm 	u_int	 i, type;
152179ef399Snicm 	char	*history_file;
153179ef399Snicm 
154179ef399Snicm 	if ((history_file = status_prompt_find_history_file()) == NULL)
155179ef399Snicm 		return;
156179ef399Snicm 	log_debug("saving history to %s", history_file);
157179ef399Snicm 
158179ef399Snicm 	f = fopen(history_file, "w");
159179ef399Snicm 	if (f == NULL) {
160179ef399Snicm 		log_debug("%s: %s", history_file, strerror(errno));
161179ef399Snicm 		free(history_file);
162179ef399Snicm 		return;
163179ef399Snicm 	}
164179ef399Snicm 	free(history_file);
165179ef399Snicm 
166bc5a8fc2Snicm 	for (type = 0; type < PROMPT_NTYPES; type++) {
167bc5a8fc2Snicm 		for (i = 0; i < status_prompt_hsize[type]; i++) {
168bc5a8fc2Snicm 			fputs(prompt_type_strings[type], f);
169bc5a8fc2Snicm 			fputc(':', f);
170bc5a8fc2Snicm 			fputs(status_prompt_hlist[type][i], f);
171179ef399Snicm 			fputc('\n', f);
172179ef399Snicm 		}
173bc5a8fc2Snicm 	}
174179ef399Snicm 	fclose(f);
175179ef399Snicm 
176179ef399Snicm }
177179ef399Snicm 
178e8f6715cSnicm /* Status timer callback. */
1799883b791Snicm static void
180d0e2e7f1Snicm status_timer_callback(__unused int fd, __unused short events, void *arg)
181e8f6715cSnicm {
182e8f6715cSnicm 	struct client	*c = arg;
183e8f6715cSnicm 	struct session	*s = c->session;
184e8f6715cSnicm 	struct timeval	 tv;
185e8f6715cSnicm 
186a4c723edSnicm 	evtimer_del(&c->status.timer);
187e8f6715cSnicm 
188e8f6715cSnicm 	if (s == NULL)
189e8f6715cSnicm 		return;
190e8f6715cSnicm 
191e8f6715cSnicm 	if (c->message_string == NULL && c->prompt_string == NULL)
192e7808201Snicm 		c->flags |= CLIENT_REDRAWSTATUS;
193e8f6715cSnicm 
194e8f6715cSnicm 	timerclear(&tv);
195d89252e5Snicm 	tv.tv_sec = options_get_number(s->options, "status-interval");
196e8f6715cSnicm 
197e8f6715cSnicm 	if (tv.tv_sec != 0)
198a4c723edSnicm 		evtimer_add(&c->status.timer, &tv);
1997ad4d2ccSnicm 	log_debug("client %p, status interval %d", c, (int)tv.tv_sec);
200e8f6715cSnicm }
201e8f6715cSnicm 
202e8f6715cSnicm /* Start status timer for client. */
203e8f6715cSnicm void
204e8f6715cSnicm status_timer_start(struct client *c)
205e8f6715cSnicm {
206e8f6715cSnicm 	struct session	*s = c->session;
207e8f6715cSnicm 
208a4c723edSnicm 	if (event_initialized(&c->status.timer))
209a4c723edSnicm 		evtimer_del(&c->status.timer);
210e8f6715cSnicm 	else
211a4c723edSnicm 		evtimer_set(&c->status.timer, status_timer_callback, c);
212e8f6715cSnicm 
213d89252e5Snicm 	if (s != NULL && options_get_number(s->options, "status"))
214e8f6715cSnicm 		status_timer_callback(-1, 0, c);
215e8f6715cSnicm }
216e8f6715cSnicm 
217e8f6715cSnicm /* Start status timer for all clients. */
218e8f6715cSnicm void
219e8f6715cSnicm status_timer_start_all(void)
220e8f6715cSnicm {
221e8f6715cSnicm 	struct client	*c;
222e8f6715cSnicm 
223e8f6715cSnicm 	TAILQ_FOREACH(c, &clients, entry)
224e8f6715cSnicm 		status_timer_start(c);
225e8f6715cSnicm }
226e8f6715cSnicm 
227e62b76d6Snicm /* Update status cache. */
228e62b76d6Snicm void
229b2140406Snicm status_update_cache(struct session *s)
230e62b76d6Snicm {
2314ffcb1c8Snicm 	s->statuslines = options_get_number(s->options, "status");
2324ffcb1c8Snicm 	if (s->statuslines == 0)
233e62b76d6Snicm 		s->statusat = -1;
234e62b76d6Snicm 	else if (options_get_number(s->options, "status-position") == 0)
235e62b76d6Snicm 		s->statusat = 0;
236e62b76d6Snicm 	else
237e62b76d6Snicm 		s->statusat = 1;
238e62b76d6Snicm }
239e62b76d6Snicm 
240be5b7d79Snicm /* Get screen line of status line. -1 means off. */
241be5b7d79Snicm int
242be5b7d79Snicm status_at_line(struct client *c)
243be5b7d79Snicm {
244be5b7d79Snicm 	struct session	*s = c->session;
245be5b7d79Snicm 
246f415a97bSnicm 	if (c->flags & (CLIENT_STATUSOFF|CLIENT_CONTROL))
247d75a8bcfSnicm 		return (-1);
248e62b76d6Snicm 	if (s->statusat != 1)
249e62b76d6Snicm 		return (s->statusat);
2507b470e93Snicm 	return (c->tty.sy - status_line_size(c));
251d75a8bcfSnicm }
252d75a8bcfSnicm 
2537b470e93Snicm /* Get size of status line for client's session. 0 means off. */
254d75a8bcfSnicm u_int
2557b470e93Snicm status_line_size(struct client *c)
256d75a8bcfSnicm {
2577b470e93Snicm 	struct session	*s = c->session;
2587b470e93Snicm 
259f415a97bSnicm 	if (c->flags & (CLIENT_STATUSOFF|CLIENT_CONTROL))
2607b470e93Snicm 		return (0);
2612077c06bSnicm 	if (s == NULL)
2622077c06bSnicm 		return (options_get_number(global_s_options, "status"));
2634ffcb1c8Snicm 	return (s->statuslines);
2640f8aceb2Snicm }
2650f8aceb2Snicm 
266c755e00aSnicm /* Get the prompt line number for client's session. 1 means at the bottom. */
267c755e00aSnicm static u_int
268c755e00aSnicm status_prompt_line_at(struct client *c)
269c755e00aSnicm {
270c755e00aSnicm 	struct session	*s = c->session;
271c755e00aSnicm 
272c755e00aSnicm 	if (c->flags & (CLIENT_STATUSOFF|CLIENT_CONTROL))
273c755e00aSnicm 		return (1);
2740cf631b5Snicm 	return (options_get_number(s->options, "message-line"));
275c755e00aSnicm }
276c755e00aSnicm 
277e048bb79Snicm /* Get window at window list position. */
2784ffcb1c8Snicm struct style_range *
2794ffcb1c8Snicm status_get_range(struct client *c, u_int x, u_int y)
280da26b0f5Snicm {
2814ffcb1c8Snicm 	struct status_line	*sl = &c->status;
2824ffcb1c8Snicm 	struct style_range	*sr;
283da26b0f5Snicm 
2844ffcb1c8Snicm 	if (y >= nitems(sl->entries))
2854ffcb1c8Snicm 		return (NULL);
2864ffcb1c8Snicm 	TAILQ_FOREACH(sr, &sl->entries[y].ranges, entry) {
2874ffcb1c8Snicm 		if (x >= sr->start && x < sr->end)
2884ffcb1c8Snicm 			return (sr);
289da26b0f5Snicm 	}
290e048bb79Snicm 	return (NULL);
291da26b0f5Snicm }
292da26b0f5Snicm 
2934ffcb1c8Snicm /* Free all ranges. */
2944ffcb1c8Snicm static void
2954ffcb1c8Snicm status_free_ranges(struct style_ranges *srs)
2964ffcb1c8Snicm {
2974ffcb1c8Snicm 	struct style_range	*sr, *sr1;
2984ffcb1c8Snicm 
2994ffcb1c8Snicm 	TAILQ_FOREACH_SAFE(sr, srs, entry, sr1) {
3004ffcb1c8Snicm 		TAILQ_REMOVE(srs, sr, entry);
3014ffcb1c8Snicm 		free(sr);
3024ffcb1c8Snicm 	}
3034ffcb1c8Snicm }
3044ffcb1c8Snicm 
3058e497f9eSnicm /* Save old status line. */
3068e497f9eSnicm static void
3078e497f9eSnicm status_push_screen(struct client *c)
3088e497f9eSnicm {
3098e497f9eSnicm 	struct status_line *sl = &c->status;
3108e497f9eSnicm 
3118e497f9eSnicm 	if (sl->active == &sl->screen) {
3128e497f9eSnicm 		sl->active = xmalloc(sizeof *sl->active);
3138e497f9eSnicm 		screen_init(sl->active, c->tty.sx, status_line_size(c), 0);
3148e497f9eSnicm 	}
3158e497f9eSnicm 	sl->references++;
3168e497f9eSnicm }
3178e497f9eSnicm 
3188e497f9eSnicm /* Restore old status line. */
3198e497f9eSnicm static void
3208e497f9eSnicm status_pop_screen(struct client *c)
3218e497f9eSnicm {
3228e497f9eSnicm 	struct status_line *sl = &c->status;
3238e497f9eSnicm 
3248e497f9eSnicm 	if (--sl->references == 0) {
3258e497f9eSnicm 		screen_free(sl->active);
3268e497f9eSnicm 		free(sl->active);
3278e497f9eSnicm 		sl->active = &sl->screen;
3288e497f9eSnicm 	}
3298e497f9eSnicm }
3308e497f9eSnicm 
331b2140406Snicm /* Initialize status line. */
332b2140406Snicm void
333b2140406Snicm status_init(struct client *c)
334b2140406Snicm {
335b2140406Snicm 	struct status_line	*sl = &c->status;
3364ffcb1c8Snicm 	u_int			 i;
3374ffcb1c8Snicm 
3384ffcb1c8Snicm 	for (i = 0; i < nitems(sl->entries); i++)
3394ffcb1c8Snicm 		TAILQ_INIT(&sl->entries[i].ranges);
340b2140406Snicm 
341b2140406Snicm 	screen_init(&sl->screen, c->tty.sx, 1, 0);
3428e497f9eSnicm 	sl->active = &sl->screen;
343b2140406Snicm }
344b2140406Snicm 
34594912b4eSnicm /* Free status line. */
34694912b4eSnicm void
34794912b4eSnicm status_free(struct client *c)
34894912b4eSnicm {
34994912b4eSnicm 	struct status_line	*sl = &c->status;
3504ffcb1c8Snicm 	u_int			 i;
3514ffcb1c8Snicm 
3524ffcb1c8Snicm 	for (i = 0; i < nitems(sl->entries); i++) {
3534ffcb1c8Snicm 		status_free_ranges(&sl->entries[i].ranges);
3544ffcb1c8Snicm 		free((void *)sl->entries[i].expanded);
3554ffcb1c8Snicm 	}
35694912b4eSnicm 
35794912b4eSnicm 	if (event_initialized(&sl->timer))
35894912b4eSnicm 		evtimer_del(&sl->timer);
35994912b4eSnicm 
3608e497f9eSnicm 	if (sl->active != &sl->screen) {
3618e497f9eSnicm 		screen_free(sl->active);
3628e497f9eSnicm 		free(sl->active);
3638e497f9eSnicm 	}
364b2140406Snicm 	screen_free(&sl->screen);
36594912b4eSnicm }
36694912b4eSnicm 
36794912b4eSnicm /* Draw status line for client. */
368311827fbSnicm int
369311827fbSnicm status_redraw(struct client *c)
370311827fbSnicm {
371b2140406Snicm 	struct status_line		*sl = &c->status;
3724ffcb1c8Snicm 	struct status_line_entry	*sle;
373311827fbSnicm 	struct session			*s = c->session;
3744ffcb1c8Snicm 	struct screen_write_ctx		 ctx;
3754ffcb1c8Snicm 	struct grid_cell		 gc;
376d90eabadSnicm 	u_int				 lines, i, n, width = c->tty.sx;
37701c0c428Snicm 	int				 flags, force = 0, changed = 0, fg, bg;
3784ffcb1c8Snicm 	struct options_entry		*o;
37984306383Snicm 	union options_value		*ov;
3804ffcb1c8Snicm 	struct format_tree		*ft;
3814ffcb1c8Snicm 	char				*expanded;
3824ffcb1c8Snicm 
3834ffcb1c8Snicm 	log_debug("%s enter", __func__);
384311827fbSnicm 
3858e497f9eSnicm 	/* Shouldn't get here if not the active screen. */
3868e497f9eSnicm 	if (sl->active != &sl->screen)
3878e497f9eSnicm 		fatalx("not the active screen");
388a058cf74Snicm 
38979fc95a8Snicm 	/* No status line? */
3907b470e93Snicm 	lines = status_line_size(c);
391d75a8bcfSnicm 	if (c->tty.sy == 0 || lines == 0)
39279fc95a8Snicm 		return (1);
39379fc95a8Snicm 
394caa5d2c6Snicm 	/* Create format tree. */
395caa5d2c6Snicm 	flags = FORMAT_STATUS;
396caa5d2c6Snicm 	if (c->flags & CLIENT_STATUSFORCE)
397caa5d2c6Snicm 		flags |= FORMAT_FORCE;
398caa5d2c6Snicm 	ft = format_create(c, NULL, FORMAT_NONE, flags);
399caa5d2c6Snicm 	format_defaults(ft, c, NULL, NULL, NULL);
400caa5d2c6Snicm 
4010f8aceb2Snicm 	/* Set up default colour. */
402caa5d2c6Snicm 	style_apply(&gc, s->options, "status-style", ft);
40301c0c428Snicm 	fg = options_get_number(s->options, "status-fg");
404d5510c2eSnicm 	if (!COLOUR_DEFAULT(fg))
40501c0c428Snicm 		gc.fg = fg;
40601c0c428Snicm 	bg = options_get_number(s->options, "status-bg");
407d5510c2eSnicm 	if (!COLOUR_DEFAULT(bg))
40801c0c428Snicm 		gc.bg = bg;
4094ffcb1c8Snicm 	if (!grid_cells_equal(&gc, &sl->style)) {
4104ffcb1c8Snicm 		force = 1;
4114ffcb1c8Snicm 		memcpy(&sl->style, &gc, sizeof sl->style);
4120f8aceb2Snicm 	}
4130f8aceb2Snicm 
4144ffcb1c8Snicm 	/* Resize the target screen. */
4154ffcb1c8Snicm 	if (screen_size_x(&sl->screen) != width ||
4164ffcb1c8Snicm 	    screen_size_y(&sl->screen) != lines) {
4174ffcb1c8Snicm 		screen_resize(&sl->screen, width, lines, 0);
41834b35fabSnicm 		changed = force = 1;
4190f8aceb2Snicm 	}
42083e83a91Snicm 	screen_write_start(&ctx, &sl->screen);
4210f8aceb2Snicm 
4224ffcb1c8Snicm 	/* Write the status lines. */
4234ffcb1c8Snicm 	o = options_get(s->options, "status-format");
424d90eabadSnicm 	if (o == NULL) {
425d90eabadSnicm 		for (n = 0; n < width * lines; n++)
426d90eabadSnicm 			screen_write_putc(&ctx, &gc, ' ');
427d90eabadSnicm 	} else {
4284ffcb1c8Snicm 		for (i = 0; i < lines; i++) {
4294ffcb1c8Snicm 			screen_write_cursormove(&ctx, 0, i, 0);
4304ffcb1c8Snicm 
43184306383Snicm 			ov = options_array_get(o, i);
43284306383Snicm 			if (ov == NULL) {
433d90eabadSnicm 				for (n = 0; n < width; n++)
434d90eabadSnicm 					screen_write_putc(&ctx, &gc, ' ');
4354ffcb1c8Snicm 				continue;
4364ffcb1c8Snicm 			}
4374ffcb1c8Snicm 			sle = &sl->entries[i];
438afe199e1Snicm 
43984306383Snicm 			expanded = format_expand_time(ft, ov->string);
4404ffcb1c8Snicm 			if (!force &&
4414ffcb1c8Snicm 			    sle->expanded != NULL &&
4424ffcb1c8Snicm 			    strcmp(expanded, sle->expanded) == 0) {
4434ffcb1c8Snicm 				free(expanded);
4444ffcb1c8Snicm 				continue;
4454ffcb1c8Snicm 			}
4464ffcb1c8Snicm 			changed = 1;
447afe199e1Snicm 
448d90eabadSnicm 			for (n = 0; n < width; n++)
449d90eabadSnicm 				screen_write_putc(&ctx, &gc, ' ');
450d90eabadSnicm 			screen_write_cursormove(&ctx, 0, i, 0);
451d90eabadSnicm 
4524ffcb1c8Snicm 			status_free_ranges(&sle->ranges);
453173e8225Snicm 			format_draw(&ctx, &gc, width, expanded, &sle->ranges,
454173e8225Snicm 			    0);
4554ffcb1c8Snicm 
4564ffcb1c8Snicm 			free(sle->expanded);
4574ffcb1c8Snicm 			sle->expanded = expanded;
4584ffcb1c8Snicm 		}
4594ffcb1c8Snicm 	}
4604ffcb1c8Snicm 	screen_write_stop(&ctx);
4614ffcb1c8Snicm 
4624ffcb1c8Snicm 	/* Free the format tree. */
463ba28090cSnicm 	format_free(ft);
464311827fbSnicm 
4654ffcb1c8Snicm 	/* Return if the status line has changed. */
4664ffcb1c8Snicm 	log_debug("%s exit: force=%d, changed=%d", __func__, force, changed);
4674ffcb1c8Snicm 	return (force || changed);
468311827fbSnicm }
469311827fbSnicm 
4701ae39a95Snicm /* Set a status line message. */
47174d4b937Snicm void
472247fdabfSnicm status_message_set(struct client *c, int delay, int ignore_styles,
473e7e79d0aSnicm     int ignore_keys, const char *fmt, ...)
474311827fbSnicm {
475311827fbSnicm 	struct timeval	 tv;
47622864d78Snicm 	va_list		 ap;
47731e1eab0Snicm 	char		*s;
47831e1eab0Snicm 
47931e1eab0Snicm 	va_start(ap, fmt);
48031e1eab0Snicm 	xvasprintf(&s, fmt, ap);
48131e1eab0Snicm 	va_end(ap);
48231e1eab0Snicm 
48331e1eab0Snicm 	log_debug("%s: %s", __func__, s);
48431e1eab0Snicm 
48531e1eab0Snicm 	if (c == NULL) {
48631e1eab0Snicm 		server_add_message("message: %s", s);
48731e1eab0Snicm 		free(s);
48831e1eab0Snicm 		return;
48931e1eab0Snicm 	}
490311827fbSnicm 
491e468f8d3Snicm 	status_message_clear(c);
4928e497f9eSnicm 	status_push_screen(c);
49331e1eab0Snicm 	c->message_string = s;
49431e1eab0Snicm 	server_add_message("%s message: %s", c->name, s);
495dca899eaSnicm 
496247fdabfSnicm 	/*
497247fdabfSnicm 	 * With delay -1, the display-time option is used; zero means wait for
498247fdabfSnicm 	 * key press; more than zero is the actual delay time in milliseconds.
499247fdabfSnicm 	 */
500247fdabfSnicm 	if (delay == -1)
501d89252e5Snicm 		delay = options_get_number(c->session->options, "display-time");
502b62d8cfbStim 	if (delay > 0) {
503311827fbSnicm 		tv.tv_sec = delay / 1000;
504311827fbSnicm 		tv.tv_usec = (delay % 1000) * 1000L;
505311827fbSnicm 
506346357b7Snicm 		if (event_initialized(&c->message_timer))
507c5e332b7Snicm 			evtimer_del(&c->message_timer);
508c5e332b7Snicm 		evtimer_set(&c->message_timer, status_message_callback, c);
509247fdabfSnicm 
510c5e332b7Snicm 		evtimer_add(&c->message_timer, &tv);
511b62d8cfbStim 	}
512311827fbSnicm 
513e7e79d0aSnicm 	if (delay != 0)
514e7e79d0aSnicm 		c->message_ignore_keys = ignore_keys;
515e7e79d0aSnicm 	c->message_ignore_styles = ignore_styles;
516e7e79d0aSnicm 
517311827fbSnicm 	c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
518e7808201Snicm 	c->flags |= CLIENT_REDRAWSTATUS;
519311827fbSnicm }
520311827fbSnicm 
5211ae39a95Snicm /* Clear status line message. */
522311827fbSnicm void
523311827fbSnicm status_message_clear(struct client *c)
524311827fbSnicm {
525311827fbSnicm 	if (c->message_string == NULL)
526311827fbSnicm 		return;
527311827fbSnicm 
5287d053cf9Snicm 	free(c->message_string);
529311827fbSnicm 	c->message_string = NULL;
530311827fbSnicm 
53194837f72Snicm 	if (c->prompt_string == NULL)
532311827fbSnicm 		c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
533e7808201Snicm 	c->flags |= CLIENT_ALLREDRAWFLAGS; /* was frozen and may have changed */
53479fc95a8Snicm 
5358e497f9eSnicm 	status_pop_screen(c);
536311827fbSnicm }
537311827fbSnicm 
5381ae39a95Snicm /* Clear status line message after timer expires. */
5399883b791Snicm static void
540d0e2e7f1Snicm status_message_callback(__unused int fd, __unused short event, void *data)
541c5e332b7Snicm {
542c5e332b7Snicm 	struct client	*c = data;
543c5e332b7Snicm 
544c5e332b7Snicm 	status_message_clear(c);
545c5e332b7Snicm }
546c5e332b7Snicm 
547311827fbSnicm /* Draw client message on status line of present else on last line. */
548311827fbSnicm int
549311827fbSnicm status_message_redraw(struct client *c)
550311827fbSnicm {
5518e497f9eSnicm 	struct status_line	*sl = &c->status;
552311827fbSnicm 	struct screen_write_ctx	 ctx;
553311827fbSnicm 	struct session		*s = c->session;
5548e497f9eSnicm 	struct screen		 old_screen;
555311827fbSnicm 	size_t			 len;
556c755e00aSnicm 	u_int			 lines, offset, messageline;
5578e497f9eSnicm 	struct grid_cell	 gc;
558caa5d2c6Snicm 	struct format_tree	*ft;
559311827fbSnicm 
560311827fbSnicm 	if (c->tty.sx == 0 || c->tty.sy == 0)
561311827fbSnicm 		return (0);
5628e497f9eSnicm 	memcpy(&old_screen, sl->active, sizeof old_screen);
563d75a8bcfSnicm 
5647b470e93Snicm 	lines = status_line_size(c);
5658e497f9eSnicm 	if (lines <= 1)
5668ff11a1cSnicm 		lines = 1;
567483c5500Snicm 	screen_init(sl->active, c->tty.sx, lines, 0);
568311827fbSnicm 
569c755e00aSnicm 	messageline = status_prompt_line_at(c);
570c755e00aSnicm 	if (messageline > lines - 1)
571c755e00aSnicm 		messageline = lines - 1;
572c755e00aSnicm 
573f650d6adSnicm 	len = screen_write_strlen("%s", c->message_string);
574311827fbSnicm 	if (len > c->tty.sx)
575311827fbSnicm 		len = c->tty.sx;
576311827fbSnicm 
577caa5d2c6Snicm 	ft = format_create_defaults(NULL, c, NULL, NULL, NULL);
578caa5d2c6Snicm 	style_apply(&gc, s->options, "message-style", ft);
579caa5d2c6Snicm 	format_free(ft);
580311827fbSnicm 
58183e83a91Snicm 	screen_write_start(&ctx, sl->active);
582c755e00aSnicm 	screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines);
583c755e00aSnicm 	screen_write_cursormove(&ctx, 0, messageline, 0);
5844ffcb1c8Snicm 	for (offset = 0; offset < c->tty.sx; offset++)
58534f5d6a0Snicm 		screen_write_putc(&ctx, &gc, ' ');
586c755e00aSnicm 	screen_write_cursormove(&ctx, 0, messageline, 0);
5874f4307f9Snicm 	if (c->message_ignore_styles)
588f650d6adSnicm 		screen_write_nputs(&ctx, len, &gc, "%s", c->message_string);
5894f4307f9Snicm 	else
590173e8225Snicm 		format_draw(&ctx, &gc, c->tty.sx, c->message_string, NULL, 0);
591311827fbSnicm 	screen_write_stop(&ctx);
592311827fbSnicm 
5938e497f9eSnicm 	if (grid_compare(sl->active->grid, old_screen.grid) == 0) {
5948e497f9eSnicm 		screen_free(&old_screen);
595311827fbSnicm 		return (0);
596311827fbSnicm 	}
5978e497f9eSnicm 	screen_free(&old_screen);
598311827fbSnicm 	return (1);
599311827fbSnicm }
600311827fbSnicm 
601af11b61dSnicm /* Accept prompt immediately. */
602af11b61dSnicm static enum cmd_retval
603af11b61dSnicm status_prompt_accept(__unused struct cmdq_item *item, void *data)
604af11b61dSnicm {
605af11b61dSnicm 	struct client	*c = data;
606af11b61dSnicm 
607af11b61dSnicm 	if (c->prompt_string != NULL) {
608af11b61dSnicm 		c->prompt_inputcb(c, c->prompt_data, "y", 1);
609af11b61dSnicm 		status_prompt_clear(c);
610af11b61dSnicm 	}
611af11b61dSnicm 	return (CMD_RETURN_NORMAL);
612af11b61dSnicm }
613af11b61dSnicm 
6141ae39a95Snicm /* Enable status line prompt. */
615311827fbSnicm void
61694adf770Snicm status_prompt_set(struct client *c, struct cmd_find_state *fs,
61794adf770Snicm     const char *msg, const char *input, prompt_input_cb inputcb,
618bc5a8fc2Snicm     prompt_free_cb freecb, void *data, int flags, enum prompt_type prompt_type)
619311827fbSnicm {
620fb0b819bSnicm 	struct format_tree	*ft;
6217e7432ecSnicm 	char			*tmp;
622fb0b819bSnicm 
6233979f694Snicm 	server_client_clear_overlay(c);
6243979f694Snicm 
62594adf770Snicm 	if (fs != NULL)
62694adf770Snicm 		ft = format_create_from_state(NULL, c, fs);
62794adf770Snicm 	else
62894adf770Snicm 		ft = format_create_defaults(NULL, c, NULL, NULL, NULL);
62901948674Snicm 
63001948674Snicm 	if (input == NULL)
63101948674Snicm 		input = "";
63201948674Snicm 	if (flags & PROMPT_NOFORMAT)
63301948674Snicm 		tmp = xstrdup(input);
63401948674Snicm 	else
635a7d9196cSnicm 		tmp = format_expand_time(ft, input);
636c538f0fcSnicm 
637e468f8d3Snicm 	status_message_clear(c);
638e468f8d3Snicm 	status_prompt_clear(c);
6398e497f9eSnicm 	status_push_screen(c);
640a058cf74Snicm 
641a7d9196cSnicm 	c->prompt_string = format_expand_time(ft, msg);
642311827fbSnicm 
6437e7432ecSnicm 	if (flags & PROMPT_INCREMENTAL) {
6447e7432ecSnicm 		c->prompt_last = xstrdup(tmp);
6457e7432ecSnicm 		c->prompt_buffer = utf8_fromcstr("");
6467e7432ecSnicm 	} else {
6477e7432ecSnicm 		c->prompt_last = NULL;
648746b61e4Snicm 		c->prompt_buffer = utf8_fromcstr(tmp);
6497e7432ecSnicm 	}
650746b61e4Snicm 	c->prompt_index = utf8_strlen(c->prompt_buffer);
651311827fbSnicm 
6523bf5ffecSnicm 	c->prompt_inputcb = inputcb;
6533bf5ffecSnicm 	c->prompt_freecb = freecb;
654311827fbSnicm 	c->prompt_data = data;
655311827fbSnicm 
656bc5a8fc2Snicm 	memset(c->prompt_hindex, 0, sizeof c->prompt_hindex);
657311827fbSnicm 
658311827fbSnicm 	c->prompt_flags = flags;
659bc5a8fc2Snicm 	c->prompt_type = prompt_type;
660ce3f476aSnicm 	c->prompt_mode = PROMPT_ENTRY;
661311827fbSnicm 
662edd3b079Snicm 	if (~flags & PROMPT_INCREMENTAL)
66332099560Snicm 		c->tty.flags |= TTY_FREEZE;
664e7808201Snicm 	c->flags |= CLIENT_REDRAWSTATUS;
665fb0b819bSnicm 
6667e7432ecSnicm 	if (flags & PROMPT_INCREMENTAL)
6677e7432ecSnicm 		c->prompt_inputcb(c, c->prompt_data, "=", 0);
668332346e2Snicm 
669746b61e4Snicm 	free(tmp);
670fb0b819bSnicm 	format_free(ft);
671af11b61dSnicm 
672af11b61dSnicm 	if ((flags & PROMPT_SINGLE) && (flags & PROMPT_ACCEPT))
673af11b61dSnicm 		cmdq_append(c, cmdq_get_callback(status_prompt_accept, c));
674311827fbSnicm }
675311827fbSnicm 
6761ae39a95Snicm /* Remove status line prompt. */
677311827fbSnicm void
678311827fbSnicm status_prompt_clear(struct client *c)
679311827fbSnicm {
680311827fbSnicm 	if (c->prompt_string == NULL)
681311827fbSnicm 		return;
682311827fbSnicm 
6833bf5ffecSnicm 	if (c->prompt_freecb != NULL && c->prompt_data != NULL)
6843bf5ffecSnicm 		c->prompt_freecb(c->prompt_data);
685e468f8d3Snicm 
6867e7432ecSnicm 	free(c->prompt_last);
6877e7432ecSnicm 	c->prompt_last = NULL;
6887e7432ecSnicm 
6897d053cf9Snicm 	free(c->prompt_string);
690311827fbSnicm 	c->prompt_string = NULL;
691311827fbSnicm 
6927d053cf9Snicm 	free(c->prompt_buffer);
693311827fbSnicm 	c->prompt_buffer = NULL;
694311827fbSnicm 
69543a68934Snicm 	free(c->prompt_saved);
69643a68934Snicm 	c->prompt_saved = NULL;
69743a68934Snicm 
698311827fbSnicm 	c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
699e7808201Snicm 	c->flags |= CLIENT_ALLREDRAWFLAGS; /* was frozen and may have changed */
70079fc95a8Snicm 
7018e497f9eSnicm 	status_pop_screen(c);
702311827fbSnicm }
703311827fbSnicm 
7041ae39a95Snicm /* Update status line prompt with a new prompt string. */
7053a89195fSnicm void
706b3a5ab26Snicm status_prompt_update(struct client *c, const char *msg, const char *input)
7073a89195fSnicm {
708fb0b819bSnicm 	struct format_tree	*ft;
709746b61e4Snicm 	char			*tmp;
710fb0b819bSnicm 
711d559dd45Snicm 	ft = format_create(c, NULL, FORMAT_NONE, 0);
712fb0b819bSnicm 	format_defaults(ft, c, NULL, NULL, NULL);
713746b61e4Snicm 
714a7d9196cSnicm 	tmp = format_expand_time(ft, input);
715fb0b819bSnicm 
7167d053cf9Snicm 	free(c->prompt_string);
717a7d9196cSnicm 	c->prompt_string = format_expand_time(ft, msg);
7183a89195fSnicm 
7197d053cf9Snicm 	free(c->prompt_buffer);
720746b61e4Snicm 	c->prompt_buffer = utf8_fromcstr(tmp);
721746b61e4Snicm 	c->prompt_index = utf8_strlen(c->prompt_buffer);
7223a89195fSnicm 
723bc5a8fc2Snicm 	memset(c->prompt_hindex, 0, sizeof c->prompt_hindex);
7243a89195fSnicm 
725e7808201Snicm 	c->flags |= CLIENT_REDRAWSTATUS;
726fb0b819bSnicm 
727746b61e4Snicm 	free(tmp);
728fb0b819bSnicm 	format_free(ft);
7293a89195fSnicm }
7303a89195fSnicm 
731*0460263cSnicm /* Redraw character. Return 1 if can continue redrawing, 0 otherwise. */
732*0460263cSnicm static int
733*0460263cSnicm status_prompt_redraw_character(struct screen_write_ctx *ctx, u_int offset,
734*0460263cSnicm     u_int pwidth, u_int *width, struct grid_cell *gc,
735*0460263cSnicm     const struct utf8_data *ud)
736*0460263cSnicm {
737*0460263cSnicm 	u_char	ch;
738*0460263cSnicm 
739*0460263cSnicm 	if (*width < offset) {
740*0460263cSnicm 		*width += ud->width;
741*0460263cSnicm 		return (1);
742*0460263cSnicm 	}
743*0460263cSnicm 	if (*width >= offset + pwidth)
744*0460263cSnicm 		return (0);
745*0460263cSnicm 	*width += ud->width;
746*0460263cSnicm 	if (*width > offset + pwidth)
747*0460263cSnicm 		return (0);
748*0460263cSnicm 
749*0460263cSnicm 	ch = *ud->data;
750*0460263cSnicm 	if (ud->size == 1 && (ch <= 0x1f || ch == 0x7f)) {
751*0460263cSnicm 		gc->data.data[0] = '^';
752*0460263cSnicm 		gc->data.data[1] = (ch == 0x7f) ? '?' : ch|0x40;
753*0460263cSnicm 		gc->data.size = gc->data.have = 2;
754*0460263cSnicm 		gc->data.width = 2;
755*0460263cSnicm 	} else
756*0460263cSnicm 		utf8_copy(&gc->data, ud);
757*0460263cSnicm 	screen_write_cell(ctx, gc);
758*0460263cSnicm 	return (1);
759*0460263cSnicm }
760*0460263cSnicm 
761*0460263cSnicm /*
762*0460263cSnicm  * Redraw quote indicator '^' if necessary. Return 1 if can continue redrawing,
763*0460263cSnicm  * 0 otherwise.
764*0460263cSnicm  */
765*0460263cSnicm static int
766*0460263cSnicm status_prompt_redraw_quote(const struct client *c, u_int pcursor,
767*0460263cSnicm     struct screen_write_ctx *ctx, u_int offset, u_int pwidth, u_int *width,
768*0460263cSnicm     struct grid_cell *gc)
769*0460263cSnicm {
770*0460263cSnicm 	struct utf8_data	ud;
771*0460263cSnicm 
772*0460263cSnicm 	if (c->prompt_flags & PROMPT_QUOTENEXT && ctx->s->cx == pcursor + 1) {
773*0460263cSnicm 		utf8_set(&ud, '^');
774*0460263cSnicm 		return (status_prompt_redraw_character(ctx, offset, pwidth,
775*0460263cSnicm 		    width, gc, &ud));
776*0460263cSnicm 	}
777*0460263cSnicm 	return (1);
778*0460263cSnicm }
779*0460263cSnicm 
780311827fbSnicm /* Draw client prompt on status line of present else on last line. */
781311827fbSnicm int
782311827fbSnicm status_prompt_redraw(struct client *c)
783311827fbSnicm {
7848e497f9eSnicm 	struct status_line	*sl = &c->status;
785311827fbSnicm 	struct screen_write_ctx	 ctx;
786311827fbSnicm 	struct session		*s = c->session;
7878e497f9eSnicm 	struct screen		 old_screen;
7882b436e6eSnicm 	u_int			 i, lines, offset, left, start, width, n;
789c755e00aSnicm 	u_int			 pcursor, pwidth, promptline;
79032099560Snicm 	struct grid_cell	 gc;
791caa5d2c6Snicm 	struct format_tree	*ft;
792311827fbSnicm 
793311827fbSnicm 	if (c->tty.sx == 0 || c->tty.sy == 0)
794311827fbSnicm 		return (0);
7958e497f9eSnicm 	memcpy(&old_screen, sl->active, sizeof old_screen);
796d75a8bcfSnicm 
7977b470e93Snicm 	lines = status_line_size(c);
7988e497f9eSnicm 	if (lines <= 1)
7998ff11a1cSnicm 		lines = 1;
8008e497f9eSnicm 	screen_init(sl->active, c->tty.sx, lines, 0);
801d75a8bcfSnicm 
8022b436e6eSnicm 	n = options_get_number(s->options, "prompt-cursor-colour");
8032b436e6eSnicm 	sl->active->default_ccolour = n;
8042b436e6eSnicm 	n = options_get_number(s->options, "prompt-cursor-style");
8052b436e6eSnicm 	screen_set_cursor_style(n, &sl->active->default_cstyle,
8062b436e6eSnicm 	    &sl->active->default_mode);
8072b436e6eSnicm 
808c755e00aSnicm 	promptline = status_prompt_line_at(c);
809c755e00aSnicm 	if (promptline > lines - 1)
810c755e00aSnicm 		promptline = lines - 1;
811c755e00aSnicm 
812caa5d2c6Snicm 	ft = format_create_defaults(NULL, c, NULL, NULL, NULL);
813ce3f476aSnicm 	if (c->prompt_mode == PROMPT_COMMAND)
814caa5d2c6Snicm 		style_apply(&gc, s->options, "message-command-style", ft);
815a8c9f839Snicm 	else
816caa5d2c6Snicm 		style_apply(&gc, s->options, "message-style", ft);
817caa5d2c6Snicm 	format_free(ft);
818311827fbSnicm 
819b492da73Snicm 	start = format_width(c->prompt_string);
820746b61e4Snicm 	if (start > c->tty.sx)
821746b61e4Snicm 		start = c->tty.sx;
822746b61e4Snicm 
82383e83a91Snicm 	screen_write_start(&ctx, sl->active);
824c755e00aSnicm 	screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines);
825c755e00aSnicm 	screen_write_cursormove(&ctx, 0, promptline, 0);
8264ffcb1c8Snicm 	for (offset = 0; offset < c->tty.sx; offset++)
82734f5d6a0Snicm 		screen_write_putc(&ctx, &gc, ' ');
828c755e00aSnicm 	screen_write_cursormove(&ctx, 0, promptline, 0);
829b492da73Snicm 	format_draw(&ctx, &gc, start, c->prompt_string, NULL, 0);
830c755e00aSnicm 	screen_write_cursormove(&ctx, start, promptline, 0);
831746b61e4Snicm 
832746b61e4Snicm 	left = c->tty.sx - start;
833746b61e4Snicm 	if (left == 0)
834746b61e4Snicm 		goto finished;
835746b61e4Snicm 
836746b61e4Snicm 	pcursor = utf8_strwidth(c->prompt_buffer, c->prompt_index);
837746b61e4Snicm 	pwidth = utf8_strwidth(c->prompt_buffer, -1);
838*0460263cSnicm 	if (c->prompt_flags & PROMPT_QUOTENEXT)
839*0460263cSnicm 		pwidth++;
840746b61e4Snicm 	if (pcursor >= left) {
841746b61e4Snicm 		/*
842746b61e4Snicm 		 * The cursor would be outside the screen so start drawing
843746b61e4Snicm 		 * with it on the right.
844746b61e4Snicm 		 */
845746b61e4Snicm 		offset = (pcursor - left) + 1;
846746b61e4Snicm 		pwidth = left;
847746b61e4Snicm 	} else
848746b61e4Snicm 		offset = 0;
849746b61e4Snicm 	if (pwidth > left)
850746b61e4Snicm 		pwidth = left;
851*0460263cSnicm 	c->prompt_cursor = start + pcursor - offset;
852746b61e4Snicm 
853746b61e4Snicm 	width = 0;
854746b61e4Snicm 	for (i = 0; c->prompt_buffer[i].size != 0; i++) {
855*0460263cSnicm 		if (!status_prompt_redraw_quote(c, pcursor, &ctx, offset,
856*0460263cSnicm 		    pwidth, &width, &gc))
857746b61e4Snicm 			break;
858*0460263cSnicm 		if (!status_prompt_redraw_character(&ctx, offset, pwidth,
859*0460263cSnicm 		    &width, &gc, &c->prompt_buffer[i]))
860746b61e4Snicm 			break;
861746b61e4Snicm 	}
862*0460263cSnicm 	status_prompt_redraw_quote(c, pcursor, &ctx, offset, pwidth, &width,
863*0460263cSnicm 	    &gc);
864746b61e4Snicm 
865746b61e4Snicm finished:
866311827fbSnicm 	screen_write_stop(&ctx);
867311827fbSnicm 
8688e497f9eSnicm 	if (grid_compare(sl->active->grid, old_screen.grid) == 0) {
8698e497f9eSnicm 		screen_free(&old_screen);
870311827fbSnicm 		return (0);
871311827fbSnicm 	}
8728e497f9eSnicm 	screen_free(&old_screen);
873311827fbSnicm 	return (1);
874311827fbSnicm }
875311827fbSnicm 
876746b61e4Snicm /* Is this a separator? */
877746b61e4Snicm static int
878746b61e4Snicm status_prompt_in_list(const char *ws, const struct utf8_data *ud)
879746b61e4Snicm {
880746b61e4Snicm 	if (ud->size != 1 || ud->width != 1)
881746b61e4Snicm 		return (0);
882746b61e4Snicm 	return (strchr(ws, *ud->data) != NULL);
883746b61e4Snicm }
884746b61e4Snicm 
885746b61e4Snicm /* Is this a space? */
886746b61e4Snicm static int
887746b61e4Snicm status_prompt_space(const struct utf8_data *ud)
888746b61e4Snicm {
889746b61e4Snicm 	if (ud->size != 1 || ud->width != 1)
890746b61e4Snicm 		return (0);
891746b61e4Snicm 	return (*ud->data == ' ');
892746b61e4Snicm }
893746b61e4Snicm 
894ce3f476aSnicm /*
895bc5a8fc2Snicm  * Translate key from vi to emacs. Return 0 to drop key, 1 to process the key
896ce3f476aSnicm  * as an emacs key; return 2 to append to the buffer.
897ce3f476aSnicm  */
898ce3f476aSnicm static int
899ce3f476aSnicm status_prompt_translate_key(struct client *c, key_code key, key_code *new_key)
900ce3f476aSnicm {
901ce3f476aSnicm 	if (c->prompt_mode == PROMPT_ENTRY) {
902ce3f476aSnicm 		switch (key) {
903719f5715Snicm 		case 'a'|KEYC_CTRL:
904719f5715Snicm 		case 'c'|KEYC_CTRL:
905719f5715Snicm 		case 'e'|KEYC_CTRL:
906719f5715Snicm 		case 'g'|KEYC_CTRL:
907719f5715Snicm 		case 'h'|KEYC_CTRL:
908ce3f476aSnicm 		case '\011': /* Tab */
909719f5715Snicm 		case 'k'|KEYC_CTRL:
910719f5715Snicm 		case 'n'|KEYC_CTRL:
911719f5715Snicm 		case 'p'|KEYC_CTRL:
912719f5715Snicm 		case 't'|KEYC_CTRL:
913719f5715Snicm 		case 'u'|KEYC_CTRL:
914e505ea96Snicm 		case 'v'|KEYC_CTRL:
915719f5715Snicm 		case 'w'|KEYC_CTRL:
916719f5715Snicm 		case 'y'|KEYC_CTRL:
917ce3f476aSnicm 		case '\n':
918ce3f476aSnicm 		case '\r':
919e2dae3e1Snicm 		case KEYC_LEFT|KEYC_CTRL:
920e2dae3e1Snicm 		case KEYC_RIGHT|KEYC_CTRL:
921ce3f476aSnicm 		case KEYC_BSPACE:
922ce3f476aSnicm 		case KEYC_DC:
923ce3f476aSnicm 		case KEYC_DOWN:
924ce3f476aSnicm 		case KEYC_END:
925ce3f476aSnicm 		case KEYC_HOME:
926ce3f476aSnicm 		case KEYC_LEFT:
927ce3f476aSnicm 		case KEYC_RIGHT:
928ce3f476aSnicm 		case KEYC_UP:
929ce3f476aSnicm 			*new_key = key;
930ce3f476aSnicm 			return (1);
931ce3f476aSnicm 		case '\033': /* Escape */
932ce3f476aSnicm 			c->prompt_mode = PROMPT_COMMAND;
933e7808201Snicm 			c->flags |= CLIENT_REDRAWSTATUS;
934ce3f476aSnicm 			return (0);
935ce3f476aSnicm 		}
936ce3f476aSnicm 		*new_key = key;
937ce3f476aSnicm 		return (2);
938ce3f476aSnicm 	}
939ce3f476aSnicm 
940ce3f476aSnicm 	switch (key) {
941e2dae3e1Snicm 	case KEYC_BSPACE:
942e2dae3e1Snicm 		*new_key = KEYC_LEFT;
943e2dae3e1Snicm 		return (1);
944ce3f476aSnicm 	case 'A':
945ce3f476aSnicm 	case 'I':
946ce3f476aSnicm 	case 'C':
947ce3f476aSnicm 	case 's':
948ce3f476aSnicm 	case 'a':
949ce3f476aSnicm 		c->prompt_mode = PROMPT_ENTRY;
950e7808201Snicm 		c->flags |= CLIENT_REDRAWSTATUS;
951ce3f476aSnicm 		break; /* switch mode and... */
952ce3f476aSnicm 	case 'S':
953ce3f476aSnicm 		c->prompt_mode = PROMPT_ENTRY;
954e7808201Snicm 		c->flags |= CLIENT_REDRAWSTATUS;
955719f5715Snicm 		*new_key = 'u'|KEYC_CTRL;
956ce3f476aSnicm 		return (1);
957ce3f476aSnicm 	case 'i':
958ce3f476aSnicm 	case '\033': /* Escape */
959ce3f476aSnicm 		c->prompt_mode = PROMPT_ENTRY;
960e7808201Snicm 		c->flags |= CLIENT_REDRAWSTATUS;
961ce3f476aSnicm 		return (0);
962ce3f476aSnicm 	}
963ce3f476aSnicm 
964ce3f476aSnicm 	switch (key) {
965ce3f476aSnicm 	case 'A':
966ce3f476aSnicm 	case '$':
967ce3f476aSnicm 		*new_key = KEYC_END;
968ce3f476aSnicm 		return (1);
969ce3f476aSnicm 	case 'I':
970ce3f476aSnicm 	case '0':
971ce3f476aSnicm 	case '^':
972ce3f476aSnicm 		*new_key = KEYC_HOME;
973ce3f476aSnicm 		return (1);
974ce3f476aSnicm 	case 'C':
975ce3f476aSnicm 	case 'D':
976719f5715Snicm 		*new_key = 'k'|KEYC_CTRL;
977ce3f476aSnicm 		return (1);
978ce3f476aSnicm 	case KEYC_BSPACE:
979ce3f476aSnicm 	case 'X':
980ce3f476aSnicm 		*new_key = KEYC_BSPACE;
981ce3f476aSnicm 		return (1);
982ce3f476aSnicm 	case 'b':
9836a385b80Snicm 		*new_key = 'b'|KEYC_META;
984ce3f476aSnicm 		return (1);
9858f36458cSnicm 	case 'B':
9868f36458cSnicm 		*new_key = 'B'|KEYC_VI;
9878f36458cSnicm 		return (1);
988ce3f476aSnicm 	case 'd':
989719f5715Snicm 		*new_key = 'u'|KEYC_CTRL;
990ce3f476aSnicm 		return (1);
991ce3f476aSnicm 	case 'e':
9928f36458cSnicm 		*new_key = 'e'|KEYC_VI;
9938f36458cSnicm 		return (1);
994ce3f476aSnicm 	case 'E':
9958f36458cSnicm 		*new_key = 'E'|KEYC_VI;
9968f36458cSnicm 		return (1);
997ce3f476aSnicm 	case 'w':
9988f36458cSnicm 		*new_key = 'w'|KEYC_VI;
9998f36458cSnicm 		return (1);
1000ce3f476aSnicm 	case 'W':
10018f36458cSnicm 		*new_key = 'W'|KEYC_VI;
1002ce3f476aSnicm 		return (1);
1003ce3f476aSnicm 	case 'p':
1004719f5715Snicm 		*new_key = 'y'|KEYC_CTRL;
1005ce3f476aSnicm 		return (1);
10062e9f6870Snicm 	case 'q':
1007719f5715Snicm 		*new_key = 'c'|KEYC_CTRL;
10082e9f6870Snicm 		return (1);
1009ce3f476aSnicm 	case 's':
1010ce3f476aSnicm 	case KEYC_DC:
1011ce3f476aSnicm 	case 'x':
1012ce3f476aSnicm 		*new_key = KEYC_DC;
1013ce3f476aSnicm 		return (1);
1014ce3f476aSnicm 	case KEYC_DOWN:
1015ce3f476aSnicm 	case 'j':
1016ce3f476aSnicm 		*new_key = KEYC_DOWN;
1017ce3f476aSnicm 		return (1);
1018ce3f476aSnicm 	case KEYC_LEFT:
1019ce3f476aSnicm 	case 'h':
1020ce3f476aSnicm 		*new_key = KEYC_LEFT;
1021ce3f476aSnicm 		return (1);
1022ce3f476aSnicm 	case 'a':
1023ce3f476aSnicm 	case KEYC_RIGHT:
1024ce3f476aSnicm 	case 'l':
1025ce3f476aSnicm 		*new_key = KEYC_RIGHT;
1026ce3f476aSnicm 		return (1);
1027ce3f476aSnicm 	case KEYC_UP:
1028ce3f476aSnicm 	case 'k':
1029ce3f476aSnicm 		*new_key = KEYC_UP;
1030ce3f476aSnicm 		return (1);
1031719f5715Snicm 	case 'h'|KEYC_CTRL:
1032719f5715Snicm 	case 'c'|KEYC_CTRL:
1033ce3f476aSnicm 	case '\n':
1034ce3f476aSnicm 	case '\r':
1035ce3f476aSnicm 		return (1);
1036ce3f476aSnicm 	}
1037ce3f476aSnicm 	return (0);
1038ce3f476aSnicm }
1039ce3f476aSnicm 
1040cfd48a7fSnicm /* Paste into prompt. */
1041cfd48a7fSnicm static int
1042cfd48a7fSnicm status_prompt_paste(struct client *c)
1043cfd48a7fSnicm {
1044cfd48a7fSnicm 	struct paste_buffer	*pb;
1045cfd48a7fSnicm 	const char		*bufdata;
1046cfd48a7fSnicm 	size_t			 size, n, bufsize;
1047cfd48a7fSnicm 	u_int			 i;
1048cfd48a7fSnicm 	struct utf8_data	*ud, *udp;
1049cfd48a7fSnicm 	enum utf8_state		 more;
1050cfd48a7fSnicm 
1051cfd48a7fSnicm 	size = utf8_strlen(c->prompt_buffer);
1052cfd48a7fSnicm 	if (c->prompt_saved != NULL) {
1053cfd48a7fSnicm 		ud = c->prompt_saved;
1054cfd48a7fSnicm 		n = utf8_strlen(c->prompt_saved);
1055cfd48a7fSnicm 	} else {
1056cfd48a7fSnicm 		if ((pb = paste_get_top(NULL)) == NULL)
1057cfd48a7fSnicm 			return (0);
1058cfd48a7fSnicm 		bufdata = paste_buffer_data(pb, &bufsize);
1059095d8c2eSnicm 		ud = udp = xreallocarray(NULL, bufsize + 1, sizeof *ud);
1060cfd48a7fSnicm 		for (i = 0; i != bufsize; /* nothing */) {
1061cfd48a7fSnicm 			more = utf8_open(udp, bufdata[i]);
1062cfd48a7fSnicm 			if (more == UTF8_MORE) {
1063cfd48a7fSnicm 				while (++i != bufsize && more == UTF8_MORE)
1064cfd48a7fSnicm 					more = utf8_append(udp, bufdata[i]);
1065cfd48a7fSnicm 				if (more == UTF8_DONE) {
1066cfd48a7fSnicm 					udp++;
1067cfd48a7fSnicm 					continue;
1068cfd48a7fSnicm 				}
1069cfd48a7fSnicm 				i -= udp->have;
1070cfd48a7fSnicm 			}
1071cfd48a7fSnicm 			if (bufdata[i] <= 31 || bufdata[i] >= 127)
1072cfd48a7fSnicm 				break;
1073cfd48a7fSnicm 			utf8_set(udp, bufdata[i]);
1074cfd48a7fSnicm 			udp++;
1075cfd48a7fSnicm 			i++;
1076cfd48a7fSnicm 		}
1077cfd48a7fSnicm 		udp->size = 0;
1078cfd48a7fSnicm 		n = udp - ud;
1079cfd48a7fSnicm 	}
1080095d8c2eSnicm 	if (n != 0) {
1081cfd48a7fSnicm 		c->prompt_buffer = xreallocarray(c->prompt_buffer, size + n + 1,
1082cfd48a7fSnicm 		    sizeof *c->prompt_buffer);
1083cfd48a7fSnicm 		if (c->prompt_index == size) {
1084cfd48a7fSnicm 			memcpy(c->prompt_buffer + c->prompt_index, ud,
1085cfd48a7fSnicm 			    n * sizeof *c->prompt_buffer);
1086cfd48a7fSnicm 			c->prompt_index += n;
1087cfd48a7fSnicm 			c->prompt_buffer[c->prompt_index].size = 0;
1088cfd48a7fSnicm 		} else {
1089cfd48a7fSnicm 			memmove(c->prompt_buffer + c->prompt_index + n,
1090cfd48a7fSnicm 			    c->prompt_buffer + c->prompt_index,
1091095d8c2eSnicm 			    (size + 1 - c->prompt_index) *
1092095d8c2eSnicm 			    sizeof *c->prompt_buffer);
1093cfd48a7fSnicm 			memcpy(c->prompt_buffer + c->prompt_index, ud,
1094cfd48a7fSnicm 			    n * sizeof *c->prompt_buffer);
1095cfd48a7fSnicm 			c->prompt_index += n;
1096cfd48a7fSnicm 		}
1097095d8c2eSnicm 	}
1098cfd48a7fSnicm 	if (ud != c->prompt_saved)
1099cfd48a7fSnicm 		free(ud);
1100cfd48a7fSnicm 	return (1);
1101cfd48a7fSnicm }
1102cfd48a7fSnicm 
1103d6317402Snicm /* Finish completion. */
1104d6317402Snicm static int
1105d6317402Snicm status_prompt_replace_complete(struct client *c, const char *s)
1106d6317402Snicm {
1107d6317402Snicm 	char			 word[64], *allocated = NULL;
1108d6317402Snicm 	size_t			 size, n, off, idx, used;
1109d6317402Snicm 	struct utf8_data	*first, *last, *ud;
1110d6317402Snicm 
111137a3aee2Snicm 	/* Work out where the cursor currently is. */
1112d6317402Snicm 	idx = c->prompt_index;
1113d6317402Snicm 	if (idx != 0)
1114d6317402Snicm 		idx--;
111537a3aee2Snicm 	size = utf8_strlen(c->prompt_buffer);
1116d6317402Snicm 
1117d6317402Snicm 	/* Find the word we are in. */
1118d6317402Snicm 	first = &c->prompt_buffer[idx];
1119d6317402Snicm 	while (first > c->prompt_buffer && !status_prompt_space(first))
1120d6317402Snicm 		first--;
1121d6317402Snicm 	while (first->size != 0 && status_prompt_space(first))
1122d6317402Snicm 		first++;
1123d6317402Snicm 	last = &c->prompt_buffer[idx];
1124d6317402Snicm 	while (last->size != 0 && !status_prompt_space(last))
1125d6317402Snicm 		last++;
1126d6317402Snicm 	while (last > c->prompt_buffer && status_prompt_space(last))
1127d6317402Snicm 		last--;
1128d6317402Snicm 	if (last->size != 0)
1129d6317402Snicm 		last++;
113037a3aee2Snicm 	if (last < first)
1131d6317402Snicm 		return (0);
1132d6317402Snicm 	if (s == NULL) {
1133d6317402Snicm 		used = 0;
1134d6317402Snicm 		for (ud = first; ud < last; ud++) {
1135d6317402Snicm 			if (used + ud->size >= sizeof word)
1136d6317402Snicm 				break;
1137d6317402Snicm 			memcpy(word + used, ud->data, ud->size);
1138d6317402Snicm 			used += ud->size;
1139d6317402Snicm 		}
1140d6317402Snicm 		if (ud != last)
1141d6317402Snicm 			return (0);
1142d6317402Snicm 		word[used] = '\0';
1143d6317402Snicm 	}
1144d6317402Snicm 
1145d6317402Snicm 	/* Try to complete it. */
1146d6317402Snicm 	if (s == NULL) {
1147d6317402Snicm 		allocated = status_prompt_complete(c, word,
1148d6317402Snicm 		    first - c->prompt_buffer);
1149d6317402Snicm 		if (allocated == NULL)
1150d6317402Snicm 			return (0);
1151d6317402Snicm 		s = allocated;
1152d6317402Snicm 	}
1153d6317402Snicm 
1154d6317402Snicm 	/* Trim out word. */
1155d6317402Snicm 	n = size - (last - c->prompt_buffer) + 1; /* with \0 */
1156d6317402Snicm 	memmove(first, last, n * sizeof *c->prompt_buffer);
1157d6317402Snicm 	size -= last - first;
1158d6317402Snicm 
1159d6317402Snicm 	/* Insert the new word. */
1160d6317402Snicm 	size += strlen(s);
1161d6317402Snicm 	off = first - c->prompt_buffer;
1162d6317402Snicm 	c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 1,
1163d6317402Snicm 	    sizeof *c->prompt_buffer);
1164d6317402Snicm 	first = c->prompt_buffer + off;
1165d6317402Snicm 	memmove(first + strlen(s), first, n * sizeof *c->prompt_buffer);
1166d6317402Snicm 	for (idx = 0; idx < strlen(s); idx++)
1167d6317402Snicm 		utf8_set(&first[idx], s[idx]);
1168d6317402Snicm 	c->prompt_index = (first - c->prompt_buffer) + strlen(s);
1169d6317402Snicm 
1170d6317402Snicm 	free(allocated);
1171d6317402Snicm 	return (1);
1172d6317402Snicm }
1173d6317402Snicm 
11748f36458cSnicm /* Prompt forward to the next beginning of a word. */
11758f36458cSnicm static void
11768f36458cSnicm status_prompt_forward_word(struct client *c, size_t size, int vi,
11778f36458cSnicm     const char *separators)
11788f36458cSnicm {
11798f36458cSnicm 	size_t		 idx = c->prompt_index;
11808f36458cSnicm 	int		 word_is_separators;
11818f36458cSnicm 
11828f36458cSnicm 	/* In emacs mode, skip until the first non-whitespace character. */
11838f36458cSnicm 	if (!vi)
11848f36458cSnicm 		while (idx != size &&
11858f36458cSnicm 		    status_prompt_space(&c->prompt_buffer[idx]))
11868f36458cSnicm 			idx++;
11878f36458cSnicm 
11888f36458cSnicm 	/* Can't move forward if we're already at the end. */
11898f36458cSnicm 	if (idx == size) {
11908f36458cSnicm 		c->prompt_index = idx;
11918f36458cSnicm 		return;
11928f36458cSnicm 	}
11938f36458cSnicm 
11948f36458cSnicm 	/* Determine the current character class (separators or not). */
11958f36458cSnicm 	word_is_separators = status_prompt_in_list(separators,
11968f36458cSnicm 	    &c->prompt_buffer[idx]) &&
11978f36458cSnicm 	    !status_prompt_space(&c->prompt_buffer[idx]);
11988f36458cSnicm 
11998f36458cSnicm 	/* Skip ahead until the first space or opposite character class. */
12008f36458cSnicm 	do {
12018f36458cSnicm 		idx++;
12028f36458cSnicm 		if (status_prompt_space(&c->prompt_buffer[idx])) {
12038f36458cSnicm 			/* In vi mode, go to the start of the next word. */
12048f36458cSnicm 			if (vi)
12058f36458cSnicm 				while (idx != size &&
12068f36458cSnicm 				    status_prompt_space(&c->prompt_buffer[idx]))
12078f36458cSnicm 					idx++;
12088f36458cSnicm 			break;
12098f36458cSnicm 		}
12108f36458cSnicm 	} while (idx != size && word_is_separators == status_prompt_in_list(
12118f36458cSnicm 	    separators, &c->prompt_buffer[idx]));
12128f36458cSnicm 
12138f36458cSnicm 	c->prompt_index = idx;
12148f36458cSnicm }
12158f36458cSnicm 
12168f36458cSnicm /* Prompt forward to the next end of a word. */
12178f36458cSnicm static void
12188f36458cSnicm status_prompt_end_word(struct client *c, size_t size, const char *separators)
12198f36458cSnicm {
12208f36458cSnicm 	size_t		 idx = c->prompt_index;
12218f36458cSnicm 	int		 word_is_separators;
12228f36458cSnicm 
12238f36458cSnicm 	/* Can't move forward if we're already at the end. */
12248f36458cSnicm 	if (idx == size)
12258f36458cSnicm 		return;
12268f36458cSnicm 
12278f36458cSnicm 	/* Find the next word. */
12288f36458cSnicm 	do {
12298f36458cSnicm 		idx++;
12308f36458cSnicm 		if (idx == size) {
12318f36458cSnicm 			c->prompt_index = idx;
12328f36458cSnicm 			return;
12338f36458cSnicm 		}
12348f36458cSnicm 	} while (status_prompt_space(&c->prompt_buffer[idx]));
12358f36458cSnicm 
12368f36458cSnicm 	/* Determine the character class (separators or not). */
12378f36458cSnicm 	word_is_separators = status_prompt_in_list(separators,
12388f36458cSnicm 	    &c->prompt_buffer[idx]);
12398f36458cSnicm 
12408f36458cSnicm 	/* Skip ahead until the next space or opposite character class. */
12418f36458cSnicm 	do {
12428f36458cSnicm 		idx++;
12438f36458cSnicm 		if (idx == size)
12448f36458cSnicm 			break;
12458f36458cSnicm 	} while (!status_prompt_space(&c->prompt_buffer[idx]) &&
12468f36458cSnicm 	    word_is_separators == status_prompt_in_list(separators,
12478f36458cSnicm 	    &c->prompt_buffer[idx]));
12488f36458cSnicm 
12498f36458cSnicm 	/* Back up to the previous character to stop at the end of the word. */
12508f36458cSnicm 	c->prompt_index = idx - 1;
12518f36458cSnicm }
12528f36458cSnicm 
12538f36458cSnicm /* Prompt backward to the previous beginning of a word. */
12548f36458cSnicm static void
12558f36458cSnicm status_prompt_backward_word(struct client *c, const char *separators)
12568f36458cSnicm {
12578f36458cSnicm 	size_t	idx = c->prompt_index;
12588f36458cSnicm 	int	word_is_separators;
12598f36458cSnicm 
12608f36458cSnicm 	/* Find non-whitespace. */
12618f36458cSnicm 	while (idx != 0) {
12628f36458cSnicm 		--idx;
12638f36458cSnicm 		if (!status_prompt_space(&c->prompt_buffer[idx]))
12648f36458cSnicm 			break;
12658f36458cSnicm 	}
12668f36458cSnicm 	word_is_separators = status_prompt_in_list(separators,
12678f36458cSnicm 	    &c->prompt_buffer[idx]);
12688f36458cSnicm 
12698f36458cSnicm 	/* Find the character before the beginning of the word. */
12708f36458cSnicm 	while (idx != 0) {
12718f36458cSnicm 		--idx;
12728f36458cSnicm 		if (status_prompt_space(&c->prompt_buffer[idx]) ||
12738f36458cSnicm 		    word_is_separators != status_prompt_in_list(separators,
12748f36458cSnicm 		    &c->prompt_buffer[idx])) {
12758f36458cSnicm 			/* Go back to the word. */
12768f36458cSnicm 			idx++;
12778f36458cSnicm 			break;
12788f36458cSnicm 		}
12798f36458cSnicm 	}
12808f36458cSnicm 	c->prompt_index = idx;
12818f36458cSnicm }
12828f36458cSnicm 
1283311827fbSnicm /* Handle keys in prompt. */
1284a3806a61Snicm int
1285885a4698Snicm status_prompt_key(struct client *c, key_code key)
1286311827fbSnicm {
1287746b61e4Snicm 	struct options		*oo = c->session->options;
1288d6317402Snicm 	char			*s, *cp, prefix = '=';
12898f36458cSnicm 	const char		*histstr, *separators = NULL, *keystring;
1290d6317402Snicm 	size_t			 size, idx;
1291d6317402Snicm 	struct utf8_data	 tmp;
12928f36458cSnicm 	int			 keys, word_is_separators;
1293311827fbSnicm 
1294c8877404Snicm 	if (c->prompt_flags & PROMPT_KEY) {
12955416581eSnicm 		keystring = key_string_lookup_key(key, 0);
1296c8877404Snicm 		c->prompt_inputcb(c, c->prompt_data, keystring, 1);
1297c8877404Snicm 		status_prompt_clear(c);
1298c8877404Snicm 		return (0);
1299c8877404Snicm 	}
1300746b61e4Snicm 	size = utf8_strlen(c->prompt_buffer);
1301a3806a61Snicm 
1302a3806a61Snicm 	if (c->prompt_flags & PROMPT_NUMERIC) {
1303a3806a61Snicm 		if (key >= '0' && key <= '9')
1304a3806a61Snicm 			goto append_key;
1305a3806a61Snicm 		s = utf8_tocstr(c->prompt_buffer);
13063bf5ffecSnicm 		c->prompt_inputcb(c, c->prompt_data, s, 1);
1307a3806a61Snicm 		status_prompt_clear(c);
1308a3806a61Snicm 		free(s);
1309a3806a61Snicm 		return (1);
1310a3806a61Snicm 	}
13115416581eSnicm 	key &= ~KEYC_MASK_FLAGS;
1312a3806a61Snicm 
1313e505ea96Snicm 	if (c->prompt_flags & (PROMPT_SINGLE|PROMPT_QUOTENEXT)) {
1314e505ea96Snicm 		if ((key & KEYC_MASK_KEY) == KEYC_BSPACE)
1315e505ea96Snicm 			key = 0x7f;
1316e505ea96Snicm 		else if ((key & KEYC_MASK_KEY) > 0x7f) {
1317e505ea96Snicm 			if (!KEYC_IS_UNICODE(key))
1318e505ea96Snicm 				return (0);
1319e505ea96Snicm 			key &= KEYC_MASK_KEY;
1320e505ea96Snicm 		} else
1321e505ea96Snicm 			key &= (key & KEYC_CTRL) ? 0x1f : KEYC_MASK_KEY;
1322*0460263cSnicm 		c->prompt_flags &= ~PROMPT_QUOTENEXT;
1323e505ea96Snicm 		goto append_key;
1324e505ea96Snicm 	}
1325e505ea96Snicm 
1326ce3f476aSnicm 	keys = options_get_number(c->session->options, "status-keys");
1327ce3f476aSnicm 	if (keys == MODEKEY_VI) {
1328ce3f476aSnicm 		switch (status_prompt_translate_key(c, key, &key)) {
1329ce3f476aSnicm 		case 1:
1330ce3f476aSnicm 			goto process_key;
1331ce3f476aSnicm 		case 2:
1332ce3f476aSnicm 			goto append_key;
1333ce3f476aSnicm 		default:
1334ce3f476aSnicm 			return (0);
1335ce3f476aSnicm 		}
1336ce3f476aSnicm 	}
1337ce3f476aSnicm 
1338ce3f476aSnicm process_key:
1339ce3f476aSnicm 	switch (key) {
1340ce3f476aSnicm 	case KEYC_LEFT:
1341719f5715Snicm 	case 'b'|KEYC_CTRL:
1342311827fbSnicm 		if (c->prompt_index > 0) {
1343311827fbSnicm 			c->prompt_index--;
1344edd3b079Snicm 			break;
1345311827fbSnicm 		}
1346311827fbSnicm 		break;
1347ce3f476aSnicm 	case KEYC_RIGHT:
1348719f5715Snicm 	case 'f'|KEYC_CTRL:
1349311827fbSnicm 		if (c->prompt_index < size) {
1350311827fbSnicm 			c->prompt_index++;
1351edd3b079Snicm 			break;
1352311827fbSnicm 		}
1353311827fbSnicm 		break;
1354ce3f476aSnicm 	case KEYC_HOME:
1355719f5715Snicm 	case 'a'|KEYC_CTRL:
1356311827fbSnicm 		if (c->prompt_index != 0) {
1357311827fbSnicm 			c->prompt_index = 0;
1358edd3b079Snicm 			break;
1359311827fbSnicm 		}
1360311827fbSnicm 		break;
1361ce3f476aSnicm 	case KEYC_END:
1362719f5715Snicm 	case 'e'|KEYC_CTRL:
1363311827fbSnicm 		if (c->prompt_index != size) {
1364311827fbSnicm 			c->prompt_index = size;
1365edd3b079Snicm 			break;
1366311827fbSnicm 		}
1367311827fbSnicm 		break;
1368ce3f476aSnicm 	case '\011': /* Tab */
1369e484fb00Snicm 		if (status_prompt_replace_complete(c, NULL))
1370edd3b079Snicm 			goto changed;
1371d6317402Snicm 		break;
1372ce3f476aSnicm 	case KEYC_BSPACE:
1373719f5715Snicm 	case 'h'|KEYC_CTRL:
1374311827fbSnicm 		if (c->prompt_index != 0) {
1375311827fbSnicm 			if (c->prompt_index == size)
1376746b61e4Snicm 				c->prompt_buffer[--c->prompt_index].size = 0;
1377311827fbSnicm 			else {
1378311827fbSnicm 				memmove(c->prompt_buffer + c->prompt_index - 1,
1379311827fbSnicm 				    c->prompt_buffer + c->prompt_index,
1380746b61e4Snicm 				    (size + 1 - c->prompt_index) *
1381746b61e4Snicm 				    sizeof *c->prompt_buffer);
1382311827fbSnicm 				c->prompt_index--;
1383311827fbSnicm 			}
1384edd3b079Snicm 			goto changed;
1385311827fbSnicm 		}
1386311827fbSnicm 		break;
1387ce3f476aSnicm 	case KEYC_DC:
1388719f5715Snicm 	case 'd'|KEYC_CTRL:
1389311827fbSnicm 		if (c->prompt_index != size) {
1390311827fbSnicm 			memmove(c->prompt_buffer + c->prompt_index,
1391311827fbSnicm 			    c->prompt_buffer + c->prompt_index + 1,
1392746b61e4Snicm 			    (size + 1 - c->prompt_index) *
1393746b61e4Snicm 			    sizeof *c->prompt_buffer);
1394edd3b079Snicm 			goto changed;
1395311827fbSnicm 		}
1396311827fbSnicm 		break;
1397719f5715Snicm 	case 'u'|KEYC_CTRL:
1398746b61e4Snicm 		c->prompt_buffer[0].size = 0;
1399b6c0765cSnicm 		c->prompt_index = 0;
1400edd3b079Snicm 		goto changed;
1401719f5715Snicm 	case 'k'|KEYC_CTRL:
1402c2b39c4eSnicm 		if (c->prompt_index < size) {
1403746b61e4Snicm 			c->prompt_buffer[c->prompt_index].size = 0;
1404edd3b079Snicm 			goto changed;
1405c2b39c4eSnicm 		}
1406c2b39c4eSnicm 		break;
1407719f5715Snicm 	case 'w'|KEYC_CTRL:
14088f36458cSnicm 		separators = options_get_string(oo, "word-separators");
1409fcf68e62Snicm 		idx = c->prompt_index;
1410fcf68e62Snicm 
14118f36458cSnicm 		/* Find non-whitespace. */
1412fcf68e62Snicm 		while (idx != 0) {
1413fcf68e62Snicm 			idx--;
14148f36458cSnicm 			if (!status_prompt_space(&c->prompt_buffer[idx]))
1415fcf68e62Snicm 				break;
1416fcf68e62Snicm 		}
14178f36458cSnicm 		word_is_separators = status_prompt_in_list(separators,
14188f36458cSnicm 		    &c->prompt_buffer[idx]);
1419fcf68e62Snicm 
14208f36458cSnicm 		/* Find the character before the beginning of the word. */
1421fcf68e62Snicm 		while (idx != 0) {
1422fcf68e62Snicm 			idx--;
14238f36458cSnicm 			if (status_prompt_space(&c->prompt_buffer[idx]) ||
14248f36458cSnicm 			    word_is_separators != status_prompt_in_list(
14258f36458cSnicm 			    separators, &c->prompt_buffer[idx])) {
1426fcf68e62Snicm 				/* Go back to the word. */
1427fcf68e62Snicm 				idx++;
1428fcf68e62Snicm 				break;
1429fcf68e62Snicm 			}
1430fcf68e62Snicm 		}
1431fcf68e62Snicm 
143243a68934Snicm 		free(c->prompt_saved);
143343a68934Snicm 		c->prompt_saved = xcalloc(sizeof *c->prompt_buffer,
143443a68934Snicm 		    (c->prompt_index - idx) + 1);
143543a68934Snicm 		memcpy(c->prompt_saved, c->prompt_buffer + idx,
143643a68934Snicm 		    (c->prompt_index - idx) * sizeof *c->prompt_buffer);
143743a68934Snicm 
1438fcf68e62Snicm 		memmove(c->prompt_buffer + idx,
1439fcf68e62Snicm 		    c->prompt_buffer + c->prompt_index,
1440746b61e4Snicm 		    (size + 1 - c->prompt_index) *
1441746b61e4Snicm 		    sizeof *c->prompt_buffer);
1442fcf68e62Snicm 		memset(c->prompt_buffer + size - (c->prompt_index - idx),
1443746b61e4Snicm 		    '\0', (c->prompt_index - idx) * sizeof *c->prompt_buffer);
1444fcf68e62Snicm 		c->prompt_index = idx;
1445746b61e4Snicm 
1446edd3b079Snicm 		goto changed;
144731ded1eaSnicm 	case KEYC_RIGHT|KEYC_CTRL:
14488f36458cSnicm 	case 'f'|KEYC_META:
14498f36458cSnicm 		separators = options_get_string(oo, "word-separators");
14508f36458cSnicm 		status_prompt_forward_word(c, size, 0, separators);
1451edd3b079Snicm 		goto changed;
14528f36458cSnicm 	case 'E'|KEYC_VI:
14538f36458cSnicm 		status_prompt_end_word(c, size, "");
14548f36458cSnicm 		goto changed;
14558f36458cSnicm 	case 'e'|KEYC_VI:
14568f36458cSnicm 		separators = options_get_string(oo, "word-separators");
14578f36458cSnicm 		status_prompt_end_word(c, size, separators);
14588f36458cSnicm 		goto changed;
14598f36458cSnicm 	case 'W'|KEYC_VI:
14608f36458cSnicm 		status_prompt_forward_word(c, size, 1, "");
14618f36458cSnicm 		goto changed;
14628f36458cSnicm 	case 'w'|KEYC_VI:
14638f36458cSnicm 		separators = options_get_string(oo, "word-separators");
14648f36458cSnicm 		status_prompt_forward_word(c, size, 1, separators);
14658f36458cSnicm 		goto changed;
14668f36458cSnicm 	case 'B'|KEYC_VI:
14678f36458cSnicm 		status_prompt_backward_word(c, "");
14688f36458cSnicm 		goto changed;
146931ded1eaSnicm 	case KEYC_LEFT|KEYC_CTRL:
14708f36458cSnicm 	case 'b'|KEYC_META:
14718f36458cSnicm 		separators = options_get_string(oo, "word-separators");
14728f36458cSnicm 		status_prompt_backward_word(c, separators);
1473edd3b079Snicm 		goto changed;
1474ce3f476aSnicm 	case KEYC_UP:
1475719f5715Snicm 	case 'p'|KEYC_CTRL:
1476bc5a8fc2Snicm 		histstr = status_prompt_up_history(c->prompt_hindex,
1477bc5a8fc2Snicm 		    c->prompt_type);
14783c9a94faSnicm 		if (histstr == NULL)
1479311827fbSnicm 			break;
14807d053cf9Snicm 		free(c->prompt_buffer);
1481746b61e4Snicm 		c->prompt_buffer = utf8_fromcstr(histstr);
1482746b61e4Snicm 		c->prompt_index = utf8_strlen(c->prompt_buffer);
1483edd3b079Snicm 		goto changed;
1484ce3f476aSnicm 	case KEYC_DOWN:
1485719f5715Snicm 	case 'n'|KEYC_CTRL:
1486bc5a8fc2Snicm 		histstr = status_prompt_down_history(c->prompt_hindex,
1487bc5a8fc2Snicm 		    c->prompt_type);
14883c9a94faSnicm 		if (histstr == NULL)
14894ee77f13Snicm 			break;
14907d053cf9Snicm 		free(c->prompt_buffer);
1491746b61e4Snicm 		c->prompt_buffer = utf8_fromcstr(histstr);
1492746b61e4Snicm 		c->prompt_index = utf8_strlen(c->prompt_buffer);
1493edd3b079Snicm 		goto changed;
1494719f5715Snicm 	case 'y'|KEYC_CTRL:
1495cfd48a7fSnicm 		if (status_prompt_paste(c))
1496edd3b079Snicm 			goto changed;
1497cfd48a7fSnicm 		break;
1498719f5715Snicm 	case 't'|KEYC_CTRL:
1499cdefdc92Snicm 		idx = c->prompt_index;
1500cdefdc92Snicm 		if (idx < size)
1501cdefdc92Snicm 			idx++;
1502cdefdc92Snicm 		if (idx >= 2) {
1503746b61e4Snicm 			utf8_copy(&tmp, &c->prompt_buffer[idx - 2]);
1504746b61e4Snicm 			utf8_copy(&c->prompt_buffer[idx - 2],
1505746b61e4Snicm 			    &c->prompt_buffer[idx - 1]);
1506746b61e4Snicm 			utf8_copy(&c->prompt_buffer[idx - 1], &tmp);
1507cdefdc92Snicm 			c->prompt_index = idx;
1508edd3b079Snicm 			goto changed;
1509cdefdc92Snicm 		}
1510cdefdc92Snicm 		break;
1511ce3f476aSnicm 	case '\r':
1512ce3f476aSnicm 	case '\n':
1513746b61e4Snicm 		s = utf8_tocstr(c->prompt_buffer);
1514746b61e4Snicm 		if (*s != '\0')
1515bc5a8fc2Snicm 			status_prompt_add_history(s, c->prompt_type);
15163bf5ffecSnicm 		if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0)
1517311827fbSnicm 			status_prompt_clear(c);
1518746b61e4Snicm 		free(s);
1519311827fbSnicm 		break;
1520ce3f476aSnicm 	case '\033': /* Escape */
1521719f5715Snicm 	case 'c'|KEYC_CTRL:
1522719f5715Snicm 	case 'g'|KEYC_CTRL:
15233bf5ffecSnicm 		if (c->prompt_inputcb(c, c->prompt_data, NULL, 1) == 0)
1524311827fbSnicm 			status_prompt_clear(c);
1525311827fbSnicm 		break;
1526719f5715Snicm 	case 'r'|KEYC_CTRL:
15277e7432ecSnicm 		if (~c->prompt_flags & PROMPT_INCREMENTAL)
15287e7432ecSnicm 			break;
15297e7432ecSnicm 		if (c->prompt_buffer[0].size == 0) {
15307e7432ecSnicm 			prefix = '=';
15317e7432ecSnicm 			free(c->prompt_buffer);
15327e7432ecSnicm 			c->prompt_buffer = utf8_fromcstr(c->prompt_last);
15337e7432ecSnicm 			c->prompt_index = utf8_strlen(c->prompt_buffer);
15347e7432ecSnicm 		} else
1535edd3b079Snicm 			prefix = '-';
1536edd3b079Snicm 		goto changed;
1537719f5715Snicm 	case 's'|KEYC_CTRL:
15387e7432ecSnicm 		if (~c->prompt_flags & PROMPT_INCREMENTAL)
15397e7432ecSnicm 			break;
15407e7432ecSnicm 		if (c->prompt_buffer[0].size == 0) {
15417e7432ecSnicm 			prefix = '=';
15427e7432ecSnicm 			free(c->prompt_buffer);
15437e7432ecSnicm 			c->prompt_buffer = utf8_fromcstr(c->prompt_last);
15447e7432ecSnicm 			c->prompt_index = utf8_strlen(c->prompt_buffer);
15457e7432ecSnicm 		} else
1546edd3b079Snicm 			prefix = '+';
1547edd3b079Snicm 		goto changed;
1548e505ea96Snicm 	case 'v'|KEYC_CTRL:
1549e505ea96Snicm 		c->prompt_flags |= PROMPT_QUOTENEXT;
1550e505ea96Snicm 		break;
1551edd3b079Snicm 	default:
1552edd3b079Snicm 		goto append_key;
1553edd3b079Snicm 	}
1554edd3b079Snicm 
1555e7808201Snicm 	c->flags |= CLIENT_REDRAWSTATUS;
1556edd3b079Snicm 	return (0);
1557a3806a61Snicm 
1558a3806a61Snicm append_key:
1559e505ea96Snicm 	if (key <= 0x7f) {
156034779269Snicm 		utf8_set(&tmp, key);
1561e505ea96Snicm 		if (key <= 0x1f || key == 0x7f)
1562e505ea96Snicm 			tmp.width = 2;
1563e505ea96Snicm 	} else if (KEYC_IS_UNICODE(key))
15646852c63bSnicm 		utf8_to_data(key, &tmp);
15658f36458cSnicm 	else
15668f36458cSnicm 		return (0);
1567746b61e4Snicm 
1568746b61e4Snicm 	c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 2,
1569746b61e4Snicm 	    sizeof *c->prompt_buffer);
1570311827fbSnicm 
1571311827fbSnicm 	if (c->prompt_index == size) {
1572746b61e4Snicm 		utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp);
1573746b61e4Snicm 		c->prompt_index++;
1574746b61e4Snicm 		c->prompt_buffer[c->prompt_index].size = 0;
1575311827fbSnicm 	} else {
1576311827fbSnicm 		memmove(c->prompt_buffer + c->prompt_index + 1,
1577311827fbSnicm 		    c->prompt_buffer + c->prompt_index,
1578746b61e4Snicm 		    (size + 1 - c->prompt_index) *
1579746b61e4Snicm 		    sizeof *c->prompt_buffer);
1580746b61e4Snicm 		utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp);
1581746b61e4Snicm 		c->prompt_index++;
1582311827fbSnicm 	}
1583311827fbSnicm 
1584311827fbSnicm 	if (c->prompt_flags & PROMPT_SINGLE) {
1585d70f1befSnicm 		if (utf8_strlen(c->prompt_buffer) != 1)
1586311827fbSnicm 			status_prompt_clear(c);
1587d70f1befSnicm 		else {
1588d70f1befSnicm 			s = utf8_tocstr(c->prompt_buffer);
1589d70f1befSnicm 			if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0)
1590746b61e4Snicm 				status_prompt_clear(c);
1591746b61e4Snicm 			free(s);
1592311827fbSnicm 		}
1593d70f1befSnicm 	}
1594311827fbSnicm 
1595edd3b079Snicm changed:
1596e7808201Snicm 	c->flags |= CLIENT_REDRAWSTATUS;
1597edd3b079Snicm 	if (c->prompt_flags & PROMPT_INCREMENTAL) {
1598edd3b079Snicm 		s = utf8_tocstr(c->prompt_buffer);
1599edd3b079Snicm 		xasprintf(&cp, "%c%s", prefix, s);
16003bf5ffecSnicm 		c->prompt_inputcb(c, c->prompt_data, cp, 0);
1601edd3b079Snicm 		free(cp);
1602edd3b079Snicm 		free(s);
1603edd3b079Snicm 	}
1604a3806a61Snicm 	return (0);
1605311827fbSnicm }
1606311827fbSnicm 
16074ee77f13Snicm /* Get previous line from the history. */
16089883b791Snicm static const char *
1609bc5a8fc2Snicm status_prompt_up_history(u_int *idx, u_int type)
1610311827fbSnicm {
16114ee77f13Snicm 	/*
1612276a572eSnicm 	 * History runs from 0 to size - 1. Index is from 0 to size. Zero is
1613276a572eSnicm 	 * empty.
16144ee77f13Snicm 	 */
16154ee77f13Snicm 
1616bc5a8fc2Snicm 	if (status_prompt_hsize[type] == 0 ||
1617bc5a8fc2Snicm 	    idx[type] == status_prompt_hsize[type])
16184ee77f13Snicm 		return (NULL);
1619bc5a8fc2Snicm 	idx[type]++;
1620bc5a8fc2Snicm 	return (status_prompt_hlist[type][status_prompt_hsize[type] - idx[type]]);
1621311827fbSnicm }
1622311827fbSnicm 
16234ee77f13Snicm /* Get next line from the history. */
16249883b791Snicm static const char *
1625bc5a8fc2Snicm status_prompt_down_history(u_int *idx, u_int type)
16264ee77f13Snicm {
1627bc5a8fc2Snicm 	if (status_prompt_hsize[type] == 0 || idx[type] == 0)
16284ee77f13Snicm 		return ("");
1629bc5a8fc2Snicm 	idx[type]--;
1630bc5a8fc2Snicm 	if (idx[type] == 0)
16314ee77f13Snicm 		return ("");
1632bc5a8fc2Snicm 	return (status_prompt_hlist[type][status_prompt_hsize[type] - idx[type]]);
16334ee77f13Snicm }
16344ee77f13Snicm 
16354ee77f13Snicm /* Add line to the history. */
16369883b791Snicm static void
1637bc5a8fc2Snicm status_prompt_add_history(const char *line, u_int type)
16384ee77f13Snicm {
1639bc5a8fc2Snicm 	u_int	i, oldsize, newsize, freecount, hlimit, new = 1;
1640bc5a8fc2Snicm 	size_t	movesize;
16414ee77f13Snicm 
1642bc5a8fc2Snicm 	oldsize = status_prompt_hsize[type];
1643bc5a8fc2Snicm 	if (oldsize > 0 &&
1644bc5a8fc2Snicm 	    strcmp(status_prompt_hlist[type][oldsize - 1], line) == 0)
1645bc5a8fc2Snicm 		new = 0;
1646bc5a8fc2Snicm 
1647bc5a8fc2Snicm 	hlimit = options_get_number(global_options, "prompt-history-limit");
1648bc5a8fc2Snicm 	if (hlimit > oldsize) {
1649bc5a8fc2Snicm 		if (new == 0)
16504ee77f13Snicm 			return;
1651bc5a8fc2Snicm 		newsize = oldsize + new;
1652bc5a8fc2Snicm 	} else {
1653bc5a8fc2Snicm 		newsize = hlimit;
1654bc5a8fc2Snicm 		freecount = oldsize + new - newsize;
1655bc5a8fc2Snicm 		if (freecount > oldsize)
1656bc5a8fc2Snicm 			freecount = oldsize;
1657bc5a8fc2Snicm 		if (freecount == 0)
1658276a572eSnicm 			return;
1659bc5a8fc2Snicm 		for (i = 0; i < freecount; i++)
1660bc5a8fc2Snicm 			free(status_prompt_hlist[type][i]);
1661bc5a8fc2Snicm 		movesize = (oldsize - freecount) *
1662bc5a8fc2Snicm 		    sizeof *status_prompt_hlist[type];
1663bc5a8fc2Snicm 		if (movesize > 0) {
1664bc5a8fc2Snicm 			memmove(&status_prompt_hlist[type][0],
1665bc5a8fc2Snicm 			    &status_prompt_hlist[type][freecount], movesize);
1666bc5a8fc2Snicm 		}
16674ee77f13Snicm 	}
16684ee77f13Snicm 
1669bc5a8fc2Snicm 	if (newsize == 0) {
1670bc5a8fc2Snicm 		free(status_prompt_hlist[type]);
1671bc5a8fc2Snicm 		status_prompt_hlist[type] = NULL;
1672bc5a8fc2Snicm 	} else if (newsize != oldsize) {
1673bc5a8fc2Snicm 		status_prompt_hlist[type] =
1674bc5a8fc2Snicm 		    xreallocarray(status_prompt_hlist[type], newsize,
1675bc5a8fc2Snicm 			sizeof *status_prompt_hlist[type]);
1676bc5a8fc2Snicm 	}
1677bc5a8fc2Snicm 
1678bc5a8fc2Snicm 	if (new == 1 && newsize > 0)
1679bc5a8fc2Snicm 		status_prompt_hlist[type][newsize - 1] = xstrdup(line);
1680bc5a8fc2Snicm 	status_prompt_hsize[type] = newsize;
1681276a572eSnicm }
1682276a572eSnicm 
168348d4b375Snicm /* Add to completion list. */
168448d4b375Snicm static void
168548d4b375Snicm status_prompt_add_list(char ***list, u_int *size, const char *s)
168648d4b375Snicm {
168748d4b375Snicm 	u_int	i;
168848d4b375Snicm 
168948d4b375Snicm 	for (i = 0; i < *size; i++) {
169048d4b375Snicm 		if (strcmp((*list)[i], s) == 0)
169148d4b375Snicm 			return;
169248d4b375Snicm 	}
169348d4b375Snicm 	*list = xreallocarray(*list, (*size) + 1, sizeof **list);
169448d4b375Snicm 	(*list)[(*size)++] = xstrdup(s);
169548d4b375Snicm }
169648d4b375Snicm 
1697276a572eSnicm /* Build completion list. */
1698d6317402Snicm static char **
1699d6317402Snicm status_prompt_complete_list(u_int *size, const char *s, int at_start)
1700276a572eSnicm {
170148d4b375Snicm 	char					**list = NULL, *tmp;
1702de8eb587Snicm 	const char				**layout, *value, *cp;
1703276a572eSnicm 	const struct cmd_entry			**cmdent;
1704276a572eSnicm 	const struct options_table_entry	 *oe;
1705de8eb587Snicm 	size_t					  slen = strlen(s), valuelen;
1706de8eb587Snicm 	struct options_entry			 *o;
170739052edfSnicm 	struct options_array_item		 *a;
1708276a572eSnicm 	const char				 *layouts[] = {
170914aabaa7Snicm 		"even-horizontal", "even-vertical",
171014aabaa7Snicm 		"main-horizontal", "main-horizontal-mirrored",
171114aabaa7Snicm 		"main-vertical", "main-vertical-mirrored", "tiled", NULL
1712276a572eSnicm 	};
1713276a572eSnicm 
1714276a572eSnicm 	*size = 0;
1715276a572eSnicm 	for (cmdent = cmd_table; *cmdent != NULL; cmdent++) {
171648d4b375Snicm 		if (strncmp((*cmdent)->name, s, slen) == 0)
171748d4b375Snicm 			status_prompt_add_list(&list, size, (*cmdent)->name);
171837a3aee2Snicm 		if ((*cmdent)->alias != NULL &&
171948d4b375Snicm 		    strncmp((*cmdent)->alias, s, slen) == 0)
172048d4b375Snicm 			status_prompt_add_list(&list, size, (*cmdent)->alias);
1721276a572eSnicm 	}
1722de8eb587Snicm 	o = options_get_only(global_options, "command-alias");
172339052edfSnicm 	if (o != NULL) {
172439052edfSnicm 		a = options_array_first(o);
172539052edfSnicm 		while (a != NULL) {
1726dbd67d4eSnicm 			value = options_array_item_value(a)->string;
172761b6b4dcSnicm 			if ((cp = strchr(value, '=')) == NULL)
172861b6b4dcSnicm 				goto next;
1729de8eb587Snicm 			valuelen = cp - value;
1730de8eb587Snicm 			if (slen > valuelen || strncmp(value, s, slen) != 0)
173139052edfSnicm 				goto next;
173239052edfSnicm 
173348d4b375Snicm 			xasprintf(&tmp, "%.*s", (int)valuelen, value);
173448d4b375Snicm 			status_prompt_add_list(&list, size, tmp);
173548d4b375Snicm 			free(tmp);
173639052edfSnicm 
173739052edfSnicm 		next:
173839052edfSnicm 			a = options_array_next(a);
1739de8eb587Snicm 		}
1740de8eb587Snicm 	}
1741d6317402Snicm 	if (at_start)
1742d6317402Snicm 		return (list);
1743d6317402Snicm 	for (oe = options_table; oe->name != NULL; oe++) {
174448d4b375Snicm 		if (strncmp(oe->name, s, slen) == 0)
174548d4b375Snicm 			status_prompt_add_list(&list, size, oe->name);
1746d6317402Snicm 	}
1747d6317402Snicm 	for (layout = layouts; *layout != NULL; layout++) {
174848d4b375Snicm 		if (strncmp(*layout, s, slen) == 0)
174948d4b375Snicm 			status_prompt_add_list(&list, size, *layout);
1750d6317402Snicm 	}
1751276a572eSnicm 	return (list);
1752276a572eSnicm }
1753276a572eSnicm 
1754276a572eSnicm /* Find longest prefix. */
17559883b791Snicm static char *
1756de8eb587Snicm status_prompt_complete_prefix(char **list, u_int size)
1757276a572eSnicm {
1758276a572eSnicm 	char	 *out;
1759276a572eSnicm 	u_int	  i;
1760276a572eSnicm 	size_t	  j;
1761276a572eSnicm 
1762552c5a37Snicm 	if (list == NULL || size == 0)
1763552c5a37Snicm 		return (NULL);
1764276a572eSnicm 	out = xstrdup(list[0]);
1765276a572eSnicm 	for (i = 1; i < size; i++) {
1766276a572eSnicm 		j = strlen(list[i]);
1767276a572eSnicm 		if (j > strlen(out))
1768276a572eSnicm 			j = strlen(out);
1769276a572eSnicm 		for (; j > 0; j--) {
1770276a572eSnicm 			if (out[j - 1] != list[i][j - 1])
1771276a572eSnicm 				out[j - 1] = '\0';
1772276a572eSnicm 		}
1773276a572eSnicm 	}
1774276a572eSnicm 	return (out);
1775311827fbSnicm }
1776311827fbSnicm 
1777d6317402Snicm /* Complete word menu callback. */
1778d6317402Snicm static void
1779d6317402Snicm status_prompt_menu_callback(__unused struct menu *menu, u_int idx, key_code key,
1780d6317402Snicm     void *data)
1781d6317402Snicm {
1782d6317402Snicm 	struct status_prompt_menu	*spm = data;
1783d6317402Snicm 	struct client			*c = spm->c;
1784d6317402Snicm 	u_int				 i;
1785d6317402Snicm 	char				*s;
1786d6317402Snicm 
1787d6317402Snicm 	if (key != KEYC_NONE) {
1788d6317402Snicm 		idx += spm->start;
1789d6317402Snicm 		if (spm->flag == '\0')
1790d6317402Snicm 			s = xstrdup(spm->list[idx]);
1791d6317402Snicm 		else
1792d6317402Snicm 			xasprintf(&s, "-%c%s", spm->flag, spm->list[idx]);
1793bc5a8fc2Snicm 		if (c->prompt_type == PROMPT_TYPE_WINDOW_TARGET) {
179437a3aee2Snicm 			free(c->prompt_buffer);
179537a3aee2Snicm 			c->prompt_buffer = utf8_fromcstr(s);
179637a3aee2Snicm 			c->prompt_index = utf8_strlen(c->prompt_buffer);
179737a3aee2Snicm 			c->flags |= CLIENT_REDRAWSTATUS;
179837a3aee2Snicm 		} else if (status_prompt_replace_complete(c, s))
1799d6317402Snicm 			c->flags |= CLIENT_REDRAWSTATUS;
1800d6317402Snicm 		free(s);
1801d6317402Snicm 	}
1802d6317402Snicm 
1803d6317402Snicm 	for (i = 0; i < spm->size; i++)
1804d6317402Snicm 		free(spm->list[i]);
1805d6317402Snicm 	free(spm->list);
1806d6317402Snicm }
1807d6317402Snicm 
1808d6317402Snicm /* Show complete word menu. */
1809d6317402Snicm static int
1810d6317402Snicm status_prompt_complete_list_menu(struct client *c, char **list, u_int size,
1811d6317402Snicm     u_int offset, char flag)
1812d6317402Snicm {
1813d6317402Snicm 	struct menu			*menu;
1814d6317402Snicm 	struct menu_item		 item;
1815d6317402Snicm 	struct status_prompt_menu	*spm;
1816d6317402Snicm 	u_int				 lines = status_line_size(c), height, i;
1817d6317402Snicm 	u_int				 py;
1818d6317402Snicm 
1819d6317402Snicm 	if (size <= 1)
1820d6317402Snicm 		return (0);
1821d6317402Snicm 	if (c->tty.sy - lines < 3)
1822d6317402Snicm 		return (0);
1823d6317402Snicm 
1824d6317402Snicm 	spm = xmalloc(sizeof *spm);
1825d6317402Snicm 	spm->c = c;
1826d6317402Snicm 	spm->size = size;
1827d6317402Snicm 	spm->list = list;
1828d6317402Snicm 	spm->flag = flag;
1829d6317402Snicm 
1830d6317402Snicm 	height = c->tty.sy - lines - 2;
1831d6317402Snicm 	if (height > 10)
1832d6317402Snicm 		height = 10;
1833d6317402Snicm 	if (height > size)
1834d6317402Snicm 		height = size;
1835d6317402Snicm 	spm->start = size - height;
1836d6317402Snicm 
1837d6317402Snicm 	menu = menu_create("");
1838d6317402Snicm 	for (i = spm->start; i < size; i++) {
1839d6317402Snicm 		item.name = list[i];
1840d6317402Snicm 		item.key = '0' + (i - spm->start);
1841d6317402Snicm 		item.command = NULL;
1842e8ab8068Snicm 		menu_add_item(menu, &item, NULL, c, NULL);
1843d6317402Snicm 	}
1844d6317402Snicm 
1845d6317402Snicm 	if (options_get_number(c->session->options, "status-position") == 0)
1846d6317402Snicm 		py = lines;
1847d6317402Snicm 	else
1848d6317402Snicm 		py = c->tty.sy - 3 - height;
1849d6317402Snicm 	offset += utf8_cstrwidth(c->prompt_string);
1850d6317402Snicm 	if (offset > 2)
1851d6317402Snicm 		offset -= 2;
1852d6317402Snicm 	else
1853d6317402Snicm 		offset = 0;
1854d6317402Snicm 
185517d7ce67Snicm 	if (menu_display(menu, MENU_NOMOUSE|MENU_TAB, 0, NULL, offset, py, c,
185619c94b00Snicm 	    BOX_LINES_DEFAULT, NULL, NULL, NULL, NULL,
185719c94b00Snicm 	    status_prompt_menu_callback, spm) != 0) {
1858d6317402Snicm 		menu_free(menu);
1859d6317402Snicm 		free(spm);
1860d6317402Snicm 		return (0);
1861d6317402Snicm 	}
1862d6317402Snicm 	return (1);
1863d6317402Snicm }
1864d6317402Snicm 
1865d6317402Snicm /* Show complete word menu. */
1866d6317402Snicm static char *
1867d6317402Snicm status_prompt_complete_window_menu(struct client *c, struct session *s,
1868e484fb00Snicm     const char *word, u_int offset, char flag)
1869d6317402Snicm {
1870d6317402Snicm 	struct menu			 *menu;
1871d6317402Snicm 	struct menu_item		  item;
1872d6317402Snicm 	struct status_prompt_menu	 *spm;
1873d6317402Snicm 	struct winlink			 *wl;
1874d6317402Snicm 	char				**list = NULL, *tmp;
1875d6317402Snicm 	u_int				  lines = status_line_size(c), height;
1876d6317402Snicm 	u_int				  py, size = 0;
1877d6317402Snicm 
1878d6317402Snicm 	if (c->tty.sy - lines < 3)
1879d6317402Snicm 		return (NULL);
1880d6317402Snicm 
1881d6317402Snicm 	spm = xmalloc(sizeof *spm);
1882d6317402Snicm 	spm->c = c;
1883d6317402Snicm 	spm->flag = flag;
1884d6317402Snicm 
1885d6317402Snicm 	height = c->tty.sy - lines - 2;
1886d6317402Snicm 	if (height > 10)
1887d6317402Snicm 		height = 10;
1888d6317402Snicm 	spm->start = 0;
1889d6317402Snicm 
1890d6317402Snicm 	menu = menu_create("");
1891d6317402Snicm 	RB_FOREACH(wl, winlinks, &s->windows) {
1892e484fb00Snicm 		if (word != NULL && *word != '\0') {
1893e484fb00Snicm 			xasprintf(&tmp, "%d", wl->idx);
1894e484fb00Snicm 			if (strncmp(tmp, word, strlen(word)) != 0) {
1895e484fb00Snicm 				free(tmp);
1896e484fb00Snicm 				continue;
1897e484fb00Snicm 			}
1898e484fb00Snicm 			free(tmp);
1899e484fb00Snicm 		}
1900e484fb00Snicm 
1901d6317402Snicm 		list = xreallocarray(list, size + 1, sizeof *list);
1902bc5a8fc2Snicm 		if (c->prompt_type == PROMPT_TYPE_WINDOW_TARGET) {
190337a3aee2Snicm 			xasprintf(&tmp, "%d (%s)", wl->idx, wl->window->name);
190437a3aee2Snicm 			xasprintf(&list[size++], "%d", wl->idx);
190537a3aee2Snicm 		} else {
1906d6317402Snicm 			xasprintf(&tmp, "%s:%d (%s)", s->name, wl->idx,
1907d6317402Snicm 			    wl->window->name);
190837a3aee2Snicm 			xasprintf(&list[size++], "%s:%d", s->name, wl->idx);
190937a3aee2Snicm 		}
1910d6317402Snicm 		item.name = tmp;
1911d6317402Snicm 		item.key = '0' + size - 1;
1912d6317402Snicm 		item.command = NULL;
19130f4e471eSnicm 		menu_add_item(menu, &item, NULL, c, NULL);
1914d6317402Snicm 		free(tmp);
1915d6317402Snicm 
1916d6317402Snicm 		if (size == height)
1917d6317402Snicm 			break;
1918d6317402Snicm 	}
1919e484fb00Snicm 	if (size == 0) {
1920e484fb00Snicm 		menu_free(menu);
1921095d8c2eSnicm 		free(spm);
1922e484fb00Snicm 		return (NULL);
1923e484fb00Snicm 	}
1924d6317402Snicm 	if (size == 1) {
1925d6317402Snicm 		menu_free(menu);
192637a3aee2Snicm 		if (flag != '\0') {
1927d6317402Snicm 			xasprintf(&tmp, "-%c%s", flag, list[0]);
1928d6317402Snicm 			free(list[0]);
192937a3aee2Snicm 		} else
193037a3aee2Snicm 			tmp = list[0];
1931d6317402Snicm 		free(list);
1932095d8c2eSnicm 		free(spm);
1933d6317402Snicm 		return (tmp);
1934d6317402Snicm 	}
1935d6317402Snicm 	if (height > size)
1936d6317402Snicm 		height = size;
1937d6317402Snicm 
1938d6317402Snicm 	spm->size = size;
1939d6317402Snicm 	spm->list = list;
1940d6317402Snicm 
1941d6317402Snicm 	if (options_get_number(c->session->options, "status-position") == 0)
1942d6317402Snicm 		py = lines;
1943d6317402Snicm 	else
1944d6317402Snicm 		py = c->tty.sy - 3 - height;
1945d6317402Snicm 	offset += utf8_cstrwidth(c->prompt_string);
1946d6317402Snicm 	if (offset > 2)
1947d6317402Snicm 		offset -= 2;
1948d6317402Snicm 	else
1949d6317402Snicm 		offset = 0;
1950d6317402Snicm 
195117d7ce67Snicm 	if (menu_display(menu, MENU_NOMOUSE|MENU_TAB, 0, NULL, offset, py, c,
195219c94b00Snicm 	    BOX_LINES_DEFAULT, NULL, NULL, NULL, NULL,
195319c94b00Snicm 	    status_prompt_menu_callback, spm) != 0) {
1954d6317402Snicm 		menu_free(menu);
1955d6317402Snicm 		free(spm);
1956d6317402Snicm 		return (NULL);
1957d6317402Snicm 	}
1958d6317402Snicm 	return (NULL);
1959d6317402Snicm }
1960d6317402Snicm 
1961d6317402Snicm /* Sort complete list. */
1962d6317402Snicm static int
1963d6317402Snicm status_prompt_complete_sort(const void *a, const void *b)
1964d6317402Snicm {
1965d6317402Snicm 	const char	**aa = (const char **)a, **bb = (const char **)b;
1966d6317402Snicm 
1967d6317402Snicm 	return (strcmp(*aa, *bb));
1968d6317402Snicm }
1969d6317402Snicm 
197037a3aee2Snicm /* Complete a session. */
197137a3aee2Snicm static char *
197237a3aee2Snicm status_prompt_complete_session(char ***list, u_int *size, const char *s,
197337a3aee2Snicm     char flag)
197437a3aee2Snicm {
197537a3aee2Snicm 	struct session	*loop;
1976552c5a37Snicm 	char		*out, *tmp, n[11];
197737a3aee2Snicm 
197837a3aee2Snicm 	RB_FOREACH(loop, sessions, &sessions) {
1979552c5a37Snicm 		if (*s == '\0' || strncmp(loop->name, s, strlen(s)) == 0) {
1980552c5a37Snicm 			*list = xreallocarray(*list, (*size) + 2,
1981552c5a37Snicm 			    sizeof **list);
198237a3aee2Snicm 			xasprintf(&(*list)[(*size)++], "%s:", loop->name);
1983552c5a37Snicm 		} else if (*s == '$') {
1984552c5a37Snicm 			xsnprintf(n, sizeof n, "%u", loop->id);
1985552c5a37Snicm 			if (s[1] == '\0' ||
1986552c5a37Snicm 			    strncmp(n, s + 1, strlen(s) - 1) == 0) {
1987552c5a37Snicm 				*list = xreallocarray(*list, (*size) + 2,
1988552c5a37Snicm 				    sizeof **list);
1989552c5a37Snicm 				xasprintf(&(*list)[(*size)++], "$%s:", n);
1990552c5a37Snicm 			}
1991552c5a37Snicm 		}
199237a3aee2Snicm 	}
199337a3aee2Snicm 	out = status_prompt_complete_prefix(*list, *size);
199437a3aee2Snicm 	if (out != NULL && flag != '\0') {
199537a3aee2Snicm 		xasprintf(&tmp, "-%c%s", flag, out);
199637a3aee2Snicm 		free(out);
199737a3aee2Snicm 		out = tmp;
199837a3aee2Snicm 	}
199937a3aee2Snicm 	return (out);
200037a3aee2Snicm }
200137a3aee2Snicm 
2002311827fbSnicm /* Complete word. */
20039883b791Snicm static char *
2004d6317402Snicm status_prompt_complete(struct client *c, const char *word, u_int offset)
2005311827fbSnicm {
200637a3aee2Snicm 	struct session	 *session;
2007d6317402Snicm 	const char	 *s, *colon;
200837a3aee2Snicm 	char		**list = NULL, *copy = NULL, *out = NULL;
2009d6317402Snicm 	char		  flag = '\0';
2010276a572eSnicm 	u_int		  size = 0, i;
2011311827fbSnicm 
2012e484fb00Snicm 	if (*word == '\0' &&
2013bc5a8fc2Snicm 	    c->prompt_type != PROMPT_TYPE_TARGET &&
2014bc5a8fc2Snicm 	    c->prompt_type != PROMPT_TYPE_WINDOW_TARGET)
2015311827fbSnicm 		return (NULL);
2016311827fbSnicm 
2017bc5a8fc2Snicm 	if (c->prompt_type != PROMPT_TYPE_TARGET &&
2018bc5a8fc2Snicm 	    c->prompt_type != PROMPT_TYPE_WINDOW_TARGET &&
201937a3aee2Snicm 	    strncmp(word, "-t", 2) != 0 &&
202037a3aee2Snicm 	    strncmp(word, "-s", 2) != 0) {
2021d6317402Snicm 		list = status_prompt_complete_list(&size, word, offset == 0);
2022276a572eSnicm 		if (size == 0)
2023276a572eSnicm 			out = NULL;
2024276a572eSnicm 		else if (size == 1)
2025276a572eSnicm 			xasprintf(&out, "%s ", list[0]);
2026276a572eSnicm 		else
2027276a572eSnicm 			out = status_prompt_complete_prefix(list, size);
2028276a572eSnicm 		goto found;
2029311827fbSnicm 	}
2030311827fbSnicm 
2031bc5a8fc2Snicm 	if (c->prompt_type == PROMPT_TYPE_TARGET ||
2032bc5a8fc2Snicm 	    c->prompt_type == PROMPT_TYPE_WINDOW_TARGET) {
203337a3aee2Snicm 		s = word;
203437a3aee2Snicm 		flag = '\0';
203537a3aee2Snicm 	} else {
2036d6317402Snicm 		s = word + 2;
2037d6317402Snicm 		flag = word[1];
2038d6317402Snicm 		offset += 2;
203937a3aee2Snicm 	}
2040e484fb00Snicm 
2041e484fb00Snicm 	/* If this is a window completion, open the window menu. */
2042bc5a8fc2Snicm 	if (c->prompt_type == PROMPT_TYPE_WINDOW_TARGET) {
2043e484fb00Snicm 		out = status_prompt_complete_window_menu(c, c->session, s,
2044e484fb00Snicm 		    offset, '\0');
2045e484fb00Snicm 		goto found;
2046e484fb00Snicm 	}
2047d6317402Snicm 	colon = strchr(s, ':');
2048311827fbSnicm 
2049d6317402Snicm 	/* If there is no colon, complete as a session. */
2050d6317402Snicm 	if (colon == NULL) {
205137a3aee2Snicm 		out = status_prompt_complete_session(&list, &size, s, flag);
2052d6317402Snicm 		goto found;
2053d6317402Snicm 	}
2054311827fbSnicm 
2055d6317402Snicm 	/* If there is a colon but no period, find session and show a menu. */
2056d6317402Snicm 	if (strchr(colon + 1, '.') == NULL) {
2057d6317402Snicm 		if (*s == ':')
2058d6317402Snicm 			session = c->session;
2059d6317402Snicm 		else {
2060d6317402Snicm 			copy = xstrdup(s);
2061d6317402Snicm 			*strchr(copy, ':') = '\0';
2062d6317402Snicm 			session = session_find(copy);
2063d6317402Snicm 			free(copy);
2064d6317402Snicm 			if (session == NULL)
2065d6317402Snicm 				goto found;
2066d6317402Snicm 		}
2067e484fb00Snicm 		out = status_prompt_complete_window_menu(c, session, colon + 1,
2068e484fb00Snicm 		    offset, flag);
2069d6317402Snicm 		if (out == NULL)
2070d6317402Snicm 			return (NULL);
2071d6317402Snicm 	}
2072276a572eSnicm 
2073276a572eSnicm found:
2074d6317402Snicm 	if (size != 0) {
2075d6317402Snicm 		qsort(list, size, sizeof *list, status_prompt_complete_sort);
2076d6317402Snicm 		for (i = 0; i < size; i++)
2077d6317402Snicm 			log_debug("complete %u: %s", i, list[i]);
2078d6317402Snicm 	}
2079d6317402Snicm 
2080d6317402Snicm 	if (out != NULL && strcmp(word, out) == 0) {
2081d6317402Snicm 		free(out);
2082d6317402Snicm 		out = NULL;
2083d6317402Snicm 	}
2084d6317402Snicm 	if (out != NULL ||
2085d6317402Snicm 	    !status_prompt_complete_list_menu(c, list, size, offset, flag)) {
2086d6317402Snicm 		for (i = 0; i < size; i++)
2087d6317402Snicm 			free(list[i]);
2088276a572eSnicm 		free(list);
2089d6317402Snicm 	}
2090276a572eSnicm 	return (out);
2091311827fbSnicm }
2092bc5a8fc2Snicm 
2093bc5a8fc2Snicm /* Return the type of the prompt as an enum. */
2094bc5a8fc2Snicm enum prompt_type
2095bc5a8fc2Snicm status_prompt_type(const char *type)
2096bc5a8fc2Snicm {
2097bc5a8fc2Snicm 	u_int	i;
2098bc5a8fc2Snicm 
2099bc5a8fc2Snicm 	for (i = 0; i < PROMPT_NTYPES; i++) {
2100bc5a8fc2Snicm 		if (strcmp(type, status_prompt_type_string(i)) == 0)
2101bc5a8fc2Snicm 			return (i);
2102bc5a8fc2Snicm 	}
2103bc5a8fc2Snicm 	return (PROMPT_TYPE_INVALID);
2104bc5a8fc2Snicm }
2105bc5a8fc2Snicm 
2106bc5a8fc2Snicm /* Accessor for prompt_type_strings. */
2107bc5a8fc2Snicm const char *
2108bc5a8fc2Snicm status_prompt_type_string(u_int type)
2109bc5a8fc2Snicm {
2110bc5a8fc2Snicm 	if (type >= PROMPT_NTYPES)
2111bc5a8fc2Snicm 		return ("invalid");
2112bc5a8fc2Snicm 	return (prompt_type_strings[type]);
2113bc5a8fc2Snicm }
2114