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