GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/tmux/status.c Lines: 0 779 0.0 %
Date: 2017-11-07 Branches: 0 527 0.0 %

Line Branch Exec Source
1
/* $OpenBSD: status.c,v 1.168 2017/05/29 20:42:53 nicm Exp $ */
2
3
/*
4
 * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
5
 *
6
 * Permission to use, copy, modify, and distribute this software for any
7
 * purpose with or without fee is hereby granted, provided that the above
8
 * copyright notice and this permission notice appear in all copies.
9
 *
10
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14
 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15
 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
 */
18
19
#include <sys/types.h>
20
#include <sys/time.h>
21
22
#include <errno.h>
23
#include <limits.h>
24
#include <stdarg.h>
25
#include <stdlib.h>
26
#include <string.h>
27
#include <time.h>
28
#include <unistd.h>
29
30
#include "tmux.h"
31
32
static char	*status_redraw_get_left(struct client *, time_t,
33
		     struct grid_cell *, size_t *);
34
static char	*status_redraw_get_right(struct client *, time_t,
35
		     struct grid_cell *, size_t *);
36
static char	*status_print(struct client *, struct winlink *, time_t,
37
		     struct grid_cell *);
38
static char	*status_replace(struct client *, struct winlink *, const char *,
39
		     time_t);
40
static void	 status_message_callback(int, short, void *);
41
static void	 status_timer_callback(int, short, void *);
42
43
static char	*status_prompt_find_history_file(void);
44
static const char *status_prompt_up_history(u_int *);
45
static const char *status_prompt_down_history(u_int *);
46
static void	 status_prompt_add_history(const char *);
47
48
static const char **status_prompt_complete_list(u_int *, const char *);
49
static char	*status_prompt_complete_prefix(const char **, u_int);
50
static char	*status_prompt_complete(struct session *, const char *);
51
52
/* Status prompt history. */
53
#define PROMPT_HISTORY 100
54
static char	**status_prompt_hlist;
55
static u_int	  status_prompt_hsize;
56
57
/* Find the history file to load/save from/to. */
58
static char *
59
status_prompt_find_history_file(void)
60
{
61
	const char	*home, *history_file;
62
	char		*path;
63
64
	history_file = options_get_string(global_options, "history-file");
65
	if (*history_file == '\0')
66
		return (NULL);
67
	if (*history_file == '/')
68
		return (xstrdup(history_file));
69
70
	if (history_file[0] != '~' || history_file[1] != '/')
71
		return (NULL);
72
	if ((home = find_home()) == NULL)
73
		return (NULL);
74
	xasprintf(&path, "%s%s", home, history_file + 1);
75
	return (path);
76
}
77
78
/* Load status prompt history from file. */
79
void
80
status_prompt_load_history(void)
81
{
82
	FILE	*f;
83
	char	*history_file, *line, *tmp;
84
	size_t	 length;
85
86
	if ((history_file = status_prompt_find_history_file()) == NULL)
87
		return;
88
	log_debug("loading history from %s", history_file);
89
90
	f = fopen(history_file, "r");
91
	if (f == NULL) {
92
		log_debug("%s: %s", history_file, strerror(errno));
93
		free(history_file);
94
		return;
95
	}
96
	free(history_file);
97
98
	for (;;) {
99
		if ((line = fgetln(f, &length)) == NULL)
100
			break;
101
102
		if (length > 0) {
103
			if (line[length - 1] == '\n') {
104
				line[length - 1] = '\0';
105
				status_prompt_add_history(line);
106
			} else {
107
				tmp = xmalloc(length + 1);
108
				memcpy(tmp, line, length);
109
				tmp[length] = '\0';
110
				status_prompt_add_history(tmp);
111
				free(tmp);
112
			}
113
		}
114
	}
115
	fclose(f);
116
}
117
118
/* Save status prompt history to file. */
119
void
120
status_prompt_save_history(void)
121
{
122
	FILE	*f;
123
	u_int	 i;
124
	char	*history_file;
125
126
	if ((history_file = status_prompt_find_history_file()) == NULL)
127
		return;
128
	log_debug("saving history to %s", history_file);
129
130
	f = fopen(history_file, "w");
131
	if (f == NULL) {
132
		log_debug("%s: %s", history_file, strerror(errno));
133
		free(history_file);
134
		return;
135
	}
136
	free(history_file);
137
138
	for (i = 0; i < status_prompt_hsize; i++) {
139
		fputs(status_prompt_hlist[i], f);
140
		fputc('\n', f);
141
	}
142
	fclose(f);
143
144
}
145
146
/* Status timer callback. */
147
static void
148
status_timer_callback(__unused int fd, __unused short events, void *arg)
149
{
150
	struct client	*c = arg;
151
	struct session	*s = c->session;
152
	struct timeval	 tv;
153
154
	evtimer_del(&c->status_timer);
155
156
	if (s == NULL)
157
		return;
158
159
	if (c->message_string == NULL && c->prompt_string == NULL)
160
		c->flags |= CLIENT_STATUS;
161
162
	timerclear(&tv);
163
	tv.tv_sec = options_get_number(s->options, "status-interval");
164
165
	if (tv.tv_sec != 0)
166
		evtimer_add(&c->status_timer, &tv);
167
	log_debug("client %p, status interval %d", c, (int)tv.tv_sec);
168
}
169
170
/* Start status timer for client. */
171
void
172
status_timer_start(struct client *c)
173
{
174
	struct session	*s = c->session;
175
176
	if (event_initialized(&c->status_timer))
177
		evtimer_del(&c->status_timer);
178
	else
179
		evtimer_set(&c->status_timer, status_timer_callback, c);
180
181
	if (s != NULL && options_get_number(s->options, "status"))
182
		status_timer_callback(-1, 0, c);
183
}
184
185
/* Start status timer for all clients. */
186
void
187
status_timer_start_all(void)
188
{
189
	struct client	*c;
190
191
	TAILQ_FOREACH(c, &clients, entry)
192
		status_timer_start(c);
193
}
194
195
/* Update status cache. */
196
void
197
status_update_saved(struct session *s)
198
{
199
	if (!options_get_number(s->options, "status"))
200
		s->statusat = -1;
201
	else if (options_get_number(s->options, "status-position") == 0)
202
		s->statusat = 0;
203
	else
204
		s->statusat = 1;
205
}
206
207
/* Get screen line of status line. -1 means off. */
208
int
209
status_at_line(struct client *c)
210
{
211
	struct session	*s = c->session;
212
213
	if (s->statusat != 1)
214
		return (s->statusat);
215
	return (c->tty.sy - 1);
216
}
217
218
/* Retrieve options for left string. */
219
static char *
220
status_redraw_get_left(struct client *c, time_t t, struct grid_cell *gc,
221
    size_t *size)
222
{
223
	struct session	*s = c->session;
224
	const char	*template;
225
	char		*left;
226
	size_t		 leftlen;
227
228
	style_apply_update(gc, s->options, "status-left-style");
229
230
	template = options_get_string(s->options, "status-left");
231
	left = status_replace(c, NULL, template, t);
232
233
	*size = options_get_number(s->options, "status-left-length");
234
	leftlen = screen_write_cstrlen("%s", left);
235
	if (leftlen < *size)
236
		*size = leftlen;
237
	return (left);
238
}
239
240
/* Retrieve options for right string. */
241
static char *
242
status_redraw_get_right(struct client *c, time_t t, struct grid_cell *gc,
243
    size_t *size)
244
{
245
	struct session	*s = c->session;
246
	const char	*template;
247
	char		*right;
248
	size_t		 rightlen;
249
250
	style_apply_update(gc, s->options, "status-right-style");
251
252
	template = options_get_string(s->options, "status-right");
253
	right = status_replace(c, NULL, template, t);
254
255
	*size = options_get_number(s->options, "status-right-length");
256
	rightlen = screen_write_cstrlen("%s", right);
257
	if (rightlen < *size)
258
		*size = rightlen;
259
	return (right);
260
}
261
262
/* Get window at window list position. */
263
struct window *
264
status_get_window_at(struct client *c, u_int x)
265
{
266
	struct session	*s = c->session;
267
	struct winlink	*wl;
268
	struct options	*oo;
269
	const char	*sep;
270
	size_t		 seplen;
271
272
	x += c->wlmouse;
273
	RB_FOREACH(wl, winlinks, &s->windows) {
274
		oo = wl->window->options;
275
276
		sep = options_get_string(oo, "window-status-separator");
277
		seplen = screen_write_cstrlen("%s", sep);
278
279
		if (x < wl->status_width)
280
			return (wl->window);
281
		x -= wl->status_width + seplen;
282
	}
283
	return (NULL);
284
}
285
286
/* Draw status for client on the last lines of given context. */
287
int
288
status_redraw(struct client *c)
289
{
290
	struct screen_write_ctx	 ctx;
291
	struct session		*s = c->session;
292
	struct winlink		*wl;
293
	struct screen		 old_status, window_list;
294
	struct grid_cell	 stdgc, lgc, rgc, gc;
295
	struct options		*oo;
296
	time_t			 t;
297
	char			*left, *right;
298
	const char		*sep;
299
	u_int			 offset, needed;
300
	u_int			 wlstart, wlwidth, wlavailable, wloffset, wlsize;
301
	size_t			 llen, rlen, seplen;
302
	int			 larrow, rarrow;
303
304
	/* Delete the saved status line, if any. */
305
	if (c->old_status != NULL) {
306
		screen_free(c->old_status);
307
		free(c->old_status);
308
		c->old_status = NULL;
309
	}
310
311
	/* No status line? */
312
	if (c->tty.sy == 0 || !options_get_number(s->options, "status"))
313
		return (1);
314
	left = right = NULL;
315
	larrow = rarrow = 0;
316
317
	/* Store current time. */
318
	t = time(NULL);
319
320
	/* Set up default colour. */
321
	style_apply(&stdgc, s->options, "status-style");
322
323
	/* Create the target screen. */
324
	memcpy(&old_status, &c->status, sizeof old_status);
325
	screen_init(&c->status, c->tty.sx, 1, 0);
326
	screen_write_start(&ctx, NULL, &c->status);
327
	for (offset = 0; offset < c->tty.sx; offset++)
328
		screen_write_putc(&ctx, &stdgc, ' ');
329
	screen_write_stop(&ctx);
330
331
	/* If the height is one line, blank status line. */
332
	if (c->tty.sy <= 1)
333
		goto out;
334
335
	/* Work out left and right strings. */
336
	memcpy(&lgc, &stdgc, sizeof lgc);
337
	left = status_redraw_get_left(c, t, &lgc, &llen);
338
	memcpy(&rgc, &stdgc, sizeof rgc);
339
	right = status_redraw_get_right(c, t, &rgc, &rlen);
340
341
	/*
342
	 * Figure out how much space we have for the window list. If there
343
	 * isn't enough space, just show a blank status line.
344
	 */
345
	needed = 0;
346
	if (llen != 0)
347
		needed += llen;
348
	if (rlen != 0)
349
		needed += rlen;
350
	if (c->tty.sx == 0 || c->tty.sx <= needed)
351
		goto out;
352
	wlavailable = c->tty.sx - needed;
353
354
	/* Calculate the total size needed for the window list. */
355
	wlstart = wloffset = wlwidth = 0;
356
	RB_FOREACH(wl, winlinks, &s->windows) {
357
		free(wl->status_text);
358
		memcpy(&wl->status_cell, &stdgc, sizeof wl->status_cell);
359
		wl->status_text = status_print(c, wl, t, &wl->status_cell);
360
		wl->status_width = screen_write_cstrlen("%s", wl->status_text);
361
362
		if (wl == s->curw)
363
			wloffset = wlwidth;
364
365
		oo = wl->window->options;
366
		sep = options_get_string(oo, "window-status-separator");
367
		seplen = screen_write_cstrlen("%s", sep);
368
		wlwidth += wl->status_width + seplen;
369
	}
370
371
	/* Create a new screen for the window list. */
372
	screen_init(&window_list, wlwidth, 1, 0);
373
374
	/* And draw the window list into it. */
375
	screen_write_start(&ctx, NULL, &window_list);
376
	RB_FOREACH(wl, winlinks, &s->windows) {
377
		screen_write_cnputs(&ctx, -1, &wl->status_cell, "%s",
378
		    wl->status_text);
379
380
		oo = wl->window->options;
381
		sep = options_get_string(oo, "window-status-separator");
382
		screen_write_cnputs(&ctx, -1, &stdgc, "%s", sep);
383
	}
384
	screen_write_stop(&ctx);
385
386
	/* If there is enough space for the total width, skip to draw now. */
387
	if (wlwidth <= wlavailable)
388
		goto draw;
389
390
	/* Find size of current window text. */
391
	wlsize = s->curw->status_width;
392
393
	/*
394
	 * If the current window is already on screen, good to draw from the
395
	 * start and just leave off the end.
396
	 */
397
	if (wloffset + wlsize < wlavailable) {
398
		if (wlavailable > 0) {
399
			rarrow = 1;
400
			wlavailable--;
401
		}
402
		wlwidth = wlavailable;
403
	} else {
404
		/*
405
		 * Work out how many characters we need to omit from the
406
		 * start. There are wlavailable characters to fill, and
407
		 * wloffset + wlsize must be the last. So, the start character
408
		 * is wloffset + wlsize - wlavailable.
409
		 */
410
		if (wlavailable > 0) {
411
			larrow = 1;
412
			wlavailable--;
413
		}
414
415
		wlstart = wloffset + wlsize - wlavailable;
416
		if (wlavailable > 0 && wlwidth > wlstart + wlavailable + 1) {
417
			rarrow = 1;
418
			wlstart++;
419
			wlavailable--;
420
		}
421
		wlwidth = wlavailable;
422
	}
423
424
	/* Bail if anything is now too small too. */
425
	if (wlwidth == 0 || wlavailable == 0) {
426
		screen_free(&window_list);
427
		goto out;
428
	}
429
430
	/*
431
	 * Now the start position is known, work out the state of the left and
432
	 * right arrows.
433
	 */
434
	offset = 0;
435
	RB_FOREACH(wl, winlinks, &s->windows) {
436
		if (wl->flags & WINLINK_ALERTFLAGS &&
437
		    larrow == 1 && offset < wlstart)
438
			larrow = -1;
439
440
		offset += wl->status_width;
441
442
		if (wl->flags & WINLINK_ALERTFLAGS &&
443
		    rarrow == 1 && offset > wlstart + wlwidth)
444
			rarrow = -1;
445
	}
446
447
draw:
448
	/* Begin drawing. */
449
	screen_write_start(&ctx, NULL, &c->status);
450
451
	/* Draw the left string and arrow. */
452
	screen_write_cursormove(&ctx, 0, 0);
453
	if (llen != 0)
454
		screen_write_cnputs(&ctx, llen, &lgc, "%s", left);
455
	if (larrow != 0) {
456
		memcpy(&gc, &stdgc, sizeof gc);
457
		if (larrow == -1)
458
			gc.attr ^= GRID_ATTR_REVERSE;
459
		screen_write_putc(&ctx, &gc, '<');
460
	}
461
462
	/* Draw the right string and arrow. */
463
	if (rarrow != 0) {
464
		screen_write_cursormove(&ctx, c->tty.sx - rlen - 1, 0);
465
		memcpy(&gc, &stdgc, sizeof gc);
466
		if (rarrow == -1)
467
			gc.attr ^= GRID_ATTR_REVERSE;
468
		screen_write_putc(&ctx, &gc, '>');
469
	} else
470
		screen_write_cursormove(&ctx, c->tty.sx - rlen, 0);
471
	if (rlen != 0)
472
		screen_write_cnputs(&ctx, rlen, &rgc, "%s", right);
473
474
	/* Figure out the offset for the window list. */
475
	if (llen != 0)
476
		wloffset = llen;
477
	else
478
		wloffset = 0;
479
	if (wlwidth < wlavailable) {
480
		switch (options_get_number(s->options, "status-justify")) {
481
		case 1:	/* centred */
482
			wloffset += (wlavailable - wlwidth) / 2;
483
			break;
484
		case 2:	/* right */
485
			wloffset += (wlavailable - wlwidth);
486
			break;
487
		}
488
	}
489
	if (larrow != 0)
490
		wloffset++;
491
492
	/* Copy the window list. */
493
	c->wlmouse = -wloffset + wlstart;
494
	screen_write_cursormove(&ctx, wloffset, 0);
495
	screen_write_copy(&ctx, &window_list, wlstart, 0, wlwidth, 1, NULL,
496
	    NULL);
497
	screen_free(&window_list);
498
499
	screen_write_stop(&ctx);
500
501
out:
502
	free(left);
503
	free(right);
504
505
	if (grid_compare(c->status.grid, old_status.grid) == 0) {
506
		screen_free(&old_status);
507
		return (0);
508
	}
509
	screen_free(&old_status);
510
	return (1);
511
}
512
513
/* Replace special sequences in fmt. */
514
static char *
515
status_replace(struct client *c, struct winlink *wl, const char *fmt, time_t t)
516
{
517
	struct format_tree	*ft;
518
	char			*expanded;
519
	u_int			 tag;
520
521
	if (fmt == NULL)
522
		return (xstrdup(""));
523
524
	if (wl != NULL)
525
		tag = FORMAT_WINDOW|wl->window->id;
526
	else
527
		tag = FORMAT_NONE;
528
	if (c->flags & CLIENT_STATUSFORCE)
529
		ft = format_create(c, NULL, tag, FORMAT_STATUS|FORMAT_FORCE);
530
	else
531
		ft = format_create(c, NULL, tag, FORMAT_STATUS);
532
	format_defaults(ft, c, NULL, wl, NULL);
533
534
	expanded = format_expand_time(ft, fmt, t);
535
536
	format_free(ft);
537
	return (expanded);
538
}
539
540
/* Return winlink status line entry and adjust gc as necessary. */
541
static char *
542
status_print(struct client *c, struct winlink *wl, time_t t,
543
    struct grid_cell *gc)
544
{
545
	struct options	*oo = wl->window->options;
546
	struct session	*s = c->session;
547
	const char	*fmt;
548
	char   		*text;
549
550
	style_apply_update(gc, oo, "window-status-style");
551
	fmt = options_get_string(oo, "window-status-format");
552
	if (wl == s->curw) {
553
		style_apply_update(gc, oo, "window-status-current-style");
554
		fmt = options_get_string(oo, "window-status-current-format");
555
	}
556
	if (wl == TAILQ_FIRST(&s->lastw))
557
		style_apply_update(gc, oo, "window-status-last-style");
558
559
	if (wl->flags & WINLINK_BELL)
560
		style_apply_update(gc, oo, "window-status-bell-style");
561
	else if (wl->flags & (WINLINK_ACTIVITY|WINLINK_SILENCE))
562
		style_apply_update(gc, oo, "window-status-activity-style");
563
564
	text = status_replace(c, wl, fmt, t);
565
	return (text);
566
}
567
568
/* Set a status line message. */
569
void
570
status_message_set(struct client *c, const char *fmt, ...)
571
{
572
	struct timeval	tv;
573
	va_list		ap;
574
	int		delay;
575
576
	status_message_clear(c);
577
578
	if (c->old_status == NULL) {
579
		c->old_status = xmalloc(sizeof *c->old_status);
580
		memcpy(c->old_status, &c->status, sizeof *c->old_status);
581
		screen_init(&c->status, c->tty.sx, 1, 0);
582
	}
583
584
	va_start(ap, fmt);
585
	xvasprintf(&c->message_string, fmt, ap);
586
	va_end(ap);
587
588
	server_client_add_message(c, "%s", c->message_string);
589
590
	delay = options_get_number(c->session->options, "display-time");
591
	if (delay > 0) {
592
		tv.tv_sec = delay / 1000;
593
		tv.tv_usec = (delay % 1000) * 1000L;
594
595
		if (event_initialized(&c->message_timer))
596
			evtimer_del(&c->message_timer);
597
		evtimer_set(&c->message_timer, status_message_callback, c);
598
		evtimer_add(&c->message_timer, &tv);
599
	}
600
601
	c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
602
	c->flags |= CLIENT_STATUS;
603
}
604
605
/* Clear status line message. */
606
void
607
status_message_clear(struct client *c)
608
{
609
	if (c->message_string == NULL)
610
		return;
611
612
	free(c->message_string);
613
	c->message_string = NULL;
614
615
	if (c->prompt_string == NULL)
616
		c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
617
	c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
618
619
	screen_reinit(&c->status);
620
}
621
622
/* Clear status line message after timer expires. */
623
static void
624
status_message_callback(__unused int fd, __unused short event, void *data)
625
{
626
	struct client	*c = data;
627
628
	status_message_clear(c);
629
}
630
631
/* Draw client message on status line of present else on last line. */
632
int
633
status_message_redraw(struct client *c)
634
{
635
	struct screen_write_ctx		ctx;
636
	struct session		       *s = c->session;
637
	struct screen		        old_status;
638
	size_t			        len;
639
	struct grid_cell		gc;
640
641
	if (c->tty.sx == 0 || c->tty.sy == 0)
642
		return (0);
643
	memcpy(&old_status, &c->status, sizeof old_status);
644
	screen_init(&c->status, c->tty.sx, 1, 0);
645
646
	len = screen_write_strlen("%s", c->message_string);
647
	if (len > c->tty.sx)
648
		len = c->tty.sx;
649
650
	style_apply(&gc, s->options, "message-style");
651
652
	screen_write_start(&ctx, NULL, &c->status);
653
654
	screen_write_cursormove(&ctx, 0, 0);
655
	screen_write_nputs(&ctx, len, &gc, "%s", c->message_string);
656
	for (; len < c->tty.sx; len++)
657
		screen_write_putc(&ctx, &gc, ' ');
658
659
	screen_write_stop(&ctx);
660
661
	if (grid_compare(c->status.grid, old_status.grid) == 0) {
662
		screen_free(&old_status);
663
		return (0);
664
	}
665
	screen_free(&old_status);
666
	return (1);
667
}
668
669
/* Enable status line prompt. */
670
void
671
status_prompt_set(struct client *c, const char *msg, const char *input,
672
    prompt_input_cb inputcb, prompt_free_cb freecb, void *data, int flags)
673
{
674
	struct format_tree	*ft;
675
	time_t			 t;
676
	char			*tmp, *cp;
677
678
	ft = format_create(c, NULL, FORMAT_NONE, 0);
679
	format_defaults(ft, c, NULL, NULL, NULL);
680
	t = time(NULL);
681
682
	if (input == NULL)
683
		input = "";
684
	if (flags & PROMPT_NOFORMAT)
685
		tmp = xstrdup(input);
686
	else
687
		tmp = format_expand_time(ft, input, t);
688
689
	status_message_clear(c);
690
	status_prompt_clear(c);
691
692
	if (c->old_status == NULL) {
693
		c->old_status = xmalloc(sizeof *c->old_status);
694
		memcpy(c->old_status, &c->status, sizeof *c->old_status);
695
		screen_init(&c->status, c->tty.sx, 1, 0);
696
	}
697
698
	c->prompt_string = format_expand_time(ft, msg, t);
699
700
	c->prompt_buffer = utf8_fromcstr(tmp);
701
	c->prompt_index = utf8_strlen(c->prompt_buffer);
702
703
	c->prompt_inputcb = inputcb;
704
	c->prompt_freecb = freecb;
705
	c->prompt_data = data;
706
707
	c->prompt_hindex = 0;
708
709
	c->prompt_flags = flags;
710
	c->prompt_mode = PROMPT_ENTRY;
711
712
	if (~flags & PROMPT_INCREMENTAL)
713
		c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
714
	c->flags |= CLIENT_STATUS;
715
716
	if ((flags & PROMPT_INCREMENTAL) && *tmp != '\0') {
717
		xasprintf(&cp, "=%s", tmp);
718
		c->prompt_inputcb(c, c->prompt_data, cp, 0);
719
		free(cp);
720
	}
721
722
	free(tmp);
723
	format_free(ft);
724
}
725
726
/* Remove status line prompt. */
727
void
728
status_prompt_clear(struct client *c)
729
{
730
	if (c->prompt_string == NULL)
731
		return;
732
733
	if (c->prompt_freecb != NULL && c->prompt_data != NULL)
734
		c->prompt_freecb(c->prompt_data);
735
736
	free(c->prompt_string);
737
	c->prompt_string = NULL;
738
739
	free(c->prompt_buffer);
740
	c->prompt_buffer = NULL;
741
742
	c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
743
	c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */
744
745
	screen_reinit(&c->status);
746
}
747
748
/* Update status line prompt with a new prompt string. */
749
void
750
status_prompt_update(struct client *c, const char *msg, const char *input)
751
{
752
	struct format_tree	*ft;
753
	time_t			 t;
754
	char			*tmp;
755
756
	ft = format_create(c, NULL, FORMAT_NONE, 0);
757
	format_defaults(ft, c, NULL, NULL, NULL);
758
759
	t = time(NULL);
760
	tmp = format_expand_time(ft, input, t);
761
762
	free(c->prompt_string);
763
	c->prompt_string = format_expand_time(ft, msg, t);
764
765
	free(c->prompt_buffer);
766
	c->prompt_buffer = utf8_fromcstr(tmp);
767
	c->prompt_index = utf8_strlen(c->prompt_buffer);
768
769
	c->prompt_hindex = 0;
770
771
	c->flags |= CLIENT_STATUS;
772
773
	free(tmp);
774
	format_free(ft);
775
}
776
777
/* Draw client prompt on status line of present else on last line. */
778
int
779
status_prompt_redraw(struct client *c)
780
{
781
	struct screen_write_ctx	 ctx;
782
	struct session		*s = c->session;
783
	struct screen		 old_status;
784
	u_int			 i, offset, left, start, pcursor, pwidth, width;
785
	struct grid_cell	 gc, cursorgc;
786
787
	if (c->tty.sx == 0 || c->tty.sy == 0)
788
		return (0);
789
	memcpy(&old_status, &c->status, sizeof old_status);
790
	screen_init(&c->status, c->tty.sx, 1, 0);
791
792
	if (c->prompt_mode == PROMPT_COMMAND)
793
		style_apply(&gc, s->options, "message-command-style");
794
	else
795
		style_apply(&gc, s->options, "message-style");
796
797
	memcpy(&cursorgc, &gc, sizeof cursorgc);
798
	cursorgc.attr ^= GRID_ATTR_REVERSE;
799
800
	start = screen_write_strlen("%s", c->prompt_string);
801
	if (start > c->tty.sx)
802
		start = c->tty.sx;
803
804
	screen_write_start(&ctx, NULL, &c->status);
805
	screen_write_cursormove(&ctx, 0, 0);
806
	screen_write_nputs(&ctx, start, &gc, "%s", c->prompt_string);
807
	while (c->status.cx < screen_size_x(&c->status))
808
		screen_write_putc(&ctx, &gc, ' ');
809
	screen_write_cursormove(&ctx, start, 0);
810
811
	left = c->tty.sx - start;
812
	if (left == 0)
813
		goto finished;
814
815
	pcursor = utf8_strwidth(c->prompt_buffer, c->prompt_index);
816
	pwidth = utf8_strwidth(c->prompt_buffer, -1);
817
	if (pcursor >= left) {
818
		/*
819
		 * The cursor would be outside the screen so start drawing
820
		 * with it on the right.
821
		 */
822
		offset = (pcursor - left) + 1;
823
		pwidth = left;
824
	} else
825
		offset = 0;
826
	if (pwidth > left)
827
		pwidth = left;
828
829
	width = 0;
830
	for (i = 0; c->prompt_buffer[i].size != 0; i++) {
831
		if (width < offset) {
832
			width += c->prompt_buffer[i].width;
833
			continue;
834
		}
835
		if (width >= offset + pwidth)
836
			break;
837
		width += c->prompt_buffer[i].width;
838
		if (width > offset + pwidth)
839
			break;
840
841
		if (i != c->prompt_index) {
842
			utf8_copy(&gc.data, &c->prompt_buffer[i]);
843
			screen_write_cell(&ctx, &gc);
844
		} else {
845
			utf8_copy(&cursorgc.data, &c->prompt_buffer[i]);
846
			screen_write_cell(&ctx, &cursorgc);
847
		}
848
	}
849
	if (c->status.cx < screen_size_x(&c->status) && c->prompt_index >= i)
850
		screen_write_putc(&ctx, &cursorgc, ' ');
851
852
finished:
853
	screen_write_stop(&ctx);
854
855
	if (grid_compare(c->status.grid, old_status.grid) == 0) {
856
		screen_free(&old_status);
857
		return (0);
858
	}
859
	screen_free(&old_status);
860
	return (1);
861
}
862
863
/* Is this a separator? */
864
static int
865
status_prompt_in_list(const char *ws, const struct utf8_data *ud)
866
{
867
	if (ud->size != 1 || ud->width != 1)
868
		return (0);
869
	return (strchr(ws, *ud->data) != NULL);
870
}
871
872
/* Is this a space? */
873
static int
874
status_prompt_space(const struct utf8_data *ud)
875
{
876
	if (ud->size != 1 || ud->width != 1)
877
		return (0);
878
	return (*ud->data == ' ');
879
}
880
881
/*
882
 * Translate key from emacs to vi. Return 0 to drop key, 1 to process the key
883
 * as an emacs key; return 2 to append to the buffer.
884
 */
885
static int
886
status_prompt_translate_key(struct client *c, key_code key, key_code *new_key)
887
{
888
	if (c->prompt_mode == PROMPT_ENTRY) {
889
		switch (key) {
890
		case '\003': /* C-c */
891
		case '\010': /* C-h */
892
		case '\011': /* Tab */
893
		case '\025': /* C-u */
894
		case '\027': /* C-w */
895
		case '\n':
896
		case '\r':
897
		case KEYC_BSPACE:
898
		case KEYC_DC:
899
		case KEYC_DOWN:
900
		case KEYC_END:
901
		case KEYC_HOME:
902
		case KEYC_LEFT:
903
		case KEYC_RIGHT:
904
		case KEYC_UP:
905
			*new_key = key;
906
			return (1);
907
		case '\033': /* Escape */
908
			c->prompt_mode = PROMPT_COMMAND;
909
			c->flags |= CLIENT_STATUS;
910
			return (0);
911
		}
912
		*new_key = key;
913
		return (2);
914
	}
915
916
	switch (key) {
917
	case 'A':
918
	case 'I':
919
	case 'C':
920
	case 's':
921
	case 'a':
922
		c->prompt_mode = PROMPT_ENTRY;
923
		c->flags |= CLIENT_STATUS;
924
		break; /* switch mode and... */
925
	case 'S':
926
		c->prompt_mode = PROMPT_ENTRY;
927
		c->flags |= CLIENT_STATUS;
928
		*new_key = '\025'; /* C-u */
929
		return (1);
930
	case 'i':
931
	case '\033': /* Escape */
932
		c->prompt_mode = PROMPT_ENTRY;
933
		c->flags |= CLIENT_STATUS;
934
		return (0);
935
	}
936
937
	switch (key) {
938
	case 'A':
939
	case '$':
940
		*new_key = KEYC_END;
941
		return (1);
942
	case 'I':
943
	case '0':
944
	case '^':
945
		*new_key = KEYC_HOME;
946
		return (1);
947
	case 'C':
948
	case 'D':
949
		*new_key = '\013'; /* C-k */
950
		return (1);
951
	case KEYC_BSPACE:
952
	case 'X':
953
		*new_key = KEYC_BSPACE;
954
		return (1);
955
	case 'b':
956
	case 'B':
957
		*new_key = 'b'|KEYC_ESCAPE;
958
		return (1);
959
	case 'd':
960
		*new_key = '\025';
961
		return (1);
962
	case 'e':
963
	case 'E':
964
	case 'w':
965
	case 'W':
966
		*new_key = 'f'|KEYC_ESCAPE;
967
		return (1);
968
	case 'p':
969
		*new_key = '\031'; /* C-y */
970
		return (1);
971
	case 's':
972
	case KEYC_DC:
973
	case 'x':
974
		*new_key = KEYC_DC;
975
		return (1);
976
	case KEYC_DOWN:
977
	case 'j':
978
		*new_key = KEYC_DOWN;
979
		return (1);
980
	case KEYC_LEFT:
981
	case 'h':
982
		*new_key = KEYC_LEFT;
983
		return (1);
984
	case 'a':
985
	case KEYC_RIGHT:
986
	case 'l':
987
		*new_key = KEYC_RIGHT;
988
		return (1);
989
	case KEYC_UP:
990
	case 'k':
991
		*new_key = KEYC_UP;
992
		return (1);
993
	case '\010' /* C-h */:
994
	case '\003' /* C-c */:
995
	case '\n':
996
	case '\r':
997
		return (1);
998
	}
999
	return (0);
1000
}
1001
1002
/* Handle keys in prompt. */
1003
int
1004
status_prompt_key(struct client *c, key_code key)
1005
{
1006
	struct options		*oo = c->session->options;
1007
	struct paste_buffer	*pb;
1008
	char			*s, *cp, word[64], prefix = '=';
1009
	const char		*histstr, *bufdata, *ws = NULL;
1010
	u_char			 ch;
1011
	size_t			 size, n, off, idx, bufsize, used;
1012
	struct utf8_data	 tmp, *first, *last, *ud;
1013
	int			 keys;
1014
1015
	size = utf8_strlen(c->prompt_buffer);
1016
1017
	if (c->prompt_flags & PROMPT_NUMERIC) {
1018
		if (key >= '0' && key <= '9')
1019
			goto append_key;
1020
		s = utf8_tocstr(c->prompt_buffer);
1021
		c->prompt_inputcb(c, c->prompt_data, s, 1);
1022
		status_prompt_clear(c);
1023
		free(s);
1024
		return (1);
1025
	}
1026
1027
	keys = options_get_number(c->session->options, "status-keys");
1028
	if (keys == MODEKEY_VI) {
1029
		switch (status_prompt_translate_key(c, key, &key)) {
1030
		case 1:
1031
			goto process_key;
1032
		case 2:
1033
			goto append_key;
1034
		default:
1035
			return (0);
1036
		}
1037
	}
1038
1039
process_key:
1040
	switch (key) {
1041
	case KEYC_LEFT:
1042
	case '\002': /* C-b */
1043
		if (c->prompt_index > 0) {
1044
			c->prompt_index--;
1045
			break;
1046
		}
1047
		break;
1048
	case KEYC_RIGHT:
1049
	case '\006': /* C-f */
1050
		if (c->prompt_index < size) {
1051
			c->prompt_index++;
1052
			break;
1053
		}
1054
		break;
1055
	case KEYC_HOME:
1056
	case '\001': /* C-a */
1057
		if (c->prompt_index != 0) {
1058
			c->prompt_index = 0;
1059
			break;
1060
		}
1061
		break;
1062
	case KEYC_END:
1063
	case '\005': /* C-e */
1064
		if (c->prompt_index != size) {
1065
			c->prompt_index = size;
1066
			break;
1067
		}
1068
		break;
1069
	case '\011': /* Tab */
1070
		if (c->prompt_buffer[0].size == 0)
1071
			break;
1072
1073
		idx = c->prompt_index;
1074
		if (idx != 0)
1075
			idx--;
1076
1077
		/* Find the word we are in. */
1078
		first = &c->prompt_buffer[idx];
1079
		while (first > c->prompt_buffer && !status_prompt_space(first))
1080
			first--;
1081
		while (first->size != 0 && status_prompt_space(first))
1082
			first++;
1083
		last = &c->prompt_buffer[idx];
1084
		while (last->size != 0 && !status_prompt_space(last))
1085
			last++;
1086
		while (last > c->prompt_buffer && status_prompt_space(last))
1087
			last--;
1088
		if (last->size != 0)
1089
			last++;
1090
		if (last <= first)
1091
			break;
1092
1093
		used = 0;
1094
		for (ud = first; ud < last; ud++) {
1095
			if (used + ud->size >= sizeof word)
1096
				break;
1097
			memcpy(word + used, ud->data, ud->size);
1098
			used += ud->size;
1099
		}
1100
		if (ud != last)
1101
			break;
1102
		word[used] = '\0';
1103
1104
		/* And try to complete it. */
1105
		if ((s = status_prompt_complete(c->session, word)) == NULL)
1106
			break;
1107
1108
		/* Trim out word. */
1109
		n = size - (last - c->prompt_buffer) + 1; /* with \0 */
1110
		memmove(first, last, n * sizeof *c->prompt_buffer);
1111
		size -= last - first;
1112
1113
		/* Insert the new word. */
1114
		size += strlen(s);
1115
		off = first - c->prompt_buffer;
1116
		c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 1,
1117
		    sizeof *c->prompt_buffer);
1118
		first = c->prompt_buffer + off;
1119
		memmove(first + strlen(s), first, n * sizeof *c->prompt_buffer);
1120
		for (idx = 0; idx < strlen(s); idx++)
1121
			utf8_set(&first[idx], s[idx]);
1122
1123
		c->prompt_index = (first - c->prompt_buffer) + strlen(s);
1124
		free(s);
1125
1126
		goto changed;
1127
	case KEYC_BSPACE:
1128
	case '\010': /* C-h */
1129
		if (c->prompt_index != 0) {
1130
			if (c->prompt_index == size)
1131
				c->prompt_buffer[--c->prompt_index].size = 0;
1132
			else {
1133
				memmove(c->prompt_buffer + c->prompt_index - 1,
1134
				    c->prompt_buffer + c->prompt_index,
1135
				    (size + 1 - c->prompt_index) *
1136
				    sizeof *c->prompt_buffer);
1137
				c->prompt_index--;
1138
			}
1139
			goto changed;
1140
		}
1141
		break;
1142
	case KEYC_DC:
1143
	case '\004': /* C-d */
1144
		if (c->prompt_index != size) {
1145
			memmove(c->prompt_buffer + c->prompt_index,
1146
			    c->prompt_buffer + c->prompt_index + 1,
1147
			    (size + 1 - c->prompt_index) *
1148
			    sizeof *c->prompt_buffer);
1149
			goto changed;
1150
		}
1151
		break;
1152
	case '\025': /* C-u */
1153
		c->prompt_buffer[0].size = 0;
1154
		c->prompt_index = 0;
1155
		goto changed;
1156
	case '\013': /* C-k */
1157
		if (c->prompt_index < size) {
1158
			c->prompt_buffer[c->prompt_index].size = 0;
1159
			goto changed;
1160
		}
1161
		break;
1162
	case '\027': /* C-w */
1163
		ws = options_get_string(oo, "word-separators");
1164
		idx = c->prompt_index;
1165
1166
		/* Find a non-separator. */
1167
		while (idx != 0) {
1168
			idx--;
1169
			if (!status_prompt_in_list(ws, &c->prompt_buffer[idx]))
1170
				break;
1171
		}
1172
1173
		/* Find the separator at the beginning of the word. */
1174
		while (idx != 0) {
1175
			idx--;
1176
			if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) {
1177
				/* Go back to the word. */
1178
				idx++;
1179
				break;
1180
			}
1181
		}
1182
1183
		memmove(c->prompt_buffer + idx,
1184
		    c->prompt_buffer + c->prompt_index,
1185
		    (size + 1 - c->prompt_index) *
1186
		    sizeof *c->prompt_buffer);
1187
		memset(c->prompt_buffer + size - (c->prompt_index - idx),
1188
		    '\0', (c->prompt_index - idx) * sizeof *c->prompt_buffer);
1189
		c->prompt_index = idx;
1190
1191
		goto changed;
1192
	case 'f'|KEYC_ESCAPE:
1193
		ws = options_get_string(oo, "word-separators");
1194
1195
		/* Find a word. */
1196
		while (c->prompt_index != size) {
1197
			idx = ++c->prompt_index;
1198
			if (!status_prompt_in_list(ws, &c->prompt_buffer[idx]))
1199
				break;
1200
		}
1201
1202
		/* Find the separator at the end of the word. */
1203
		while (c->prompt_index != size) {
1204
			idx = ++c->prompt_index;
1205
			if (status_prompt_in_list(ws, &c->prompt_buffer[idx]))
1206
				break;
1207
		}
1208
1209
		/* Back up to the end-of-word like vi. */
1210
		if (options_get_number(oo, "status-keys") == MODEKEY_VI &&
1211
		    c->prompt_index != 0)
1212
			c->prompt_index--;
1213
1214
		goto changed;
1215
	case 'b'|KEYC_ESCAPE:
1216
		ws = options_get_string(oo, "word-separators");
1217
1218
		/* Find a non-separator. */
1219
		while (c->prompt_index != 0) {
1220
			idx = --c->prompt_index;
1221
			if (!status_prompt_in_list(ws, &c->prompt_buffer[idx]))
1222
				break;
1223
		}
1224
1225
		/* Find the separator at the beginning of the word. */
1226
		while (c->prompt_index != 0) {
1227
			idx = --c->prompt_index;
1228
			if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) {
1229
				/* Go back to the word. */
1230
				c->prompt_index++;
1231
				break;
1232
			}
1233
		}
1234
		goto changed;
1235
	case KEYC_UP:
1236
	case '\020': /* C-p */
1237
		histstr = status_prompt_up_history(&c->prompt_hindex);
1238
		if (histstr == NULL)
1239
			break;
1240
		free(c->prompt_buffer);
1241
		c->prompt_buffer = utf8_fromcstr(histstr);
1242
		c->prompt_index = utf8_strlen(c->prompt_buffer);
1243
		goto changed;
1244
	case KEYC_DOWN:
1245
	case '\016': /* C-n */
1246
		histstr = status_prompt_down_history(&c->prompt_hindex);
1247
		if (histstr == NULL)
1248
			break;
1249
		free(c->prompt_buffer);
1250
		c->prompt_buffer = utf8_fromcstr(histstr);
1251
		c->prompt_index = utf8_strlen(c->prompt_buffer);
1252
		goto changed;
1253
	case '\031': /* C-y */
1254
		if ((pb = paste_get_top(NULL)) == NULL)
1255
			break;
1256
		bufdata = paste_buffer_data(pb, &bufsize);
1257
		for (n = 0; n < bufsize; n++) {
1258
			ch = (u_char)bufdata[n];
1259
			if (ch < 32 || ch >= 127)
1260
				break;
1261
		}
1262
1263
		c->prompt_buffer = xreallocarray(c->prompt_buffer, size + n + 1,
1264
		    sizeof *c->prompt_buffer);
1265
		if (c->prompt_index == size) {
1266
			for (idx = 0; idx < n; idx++) {
1267
				ud = &c->prompt_buffer[c->prompt_index + idx];
1268
				utf8_set(ud, bufdata[idx]);
1269
			}
1270
			c->prompt_index += n;
1271
			c->prompt_buffer[c->prompt_index].size = 0;
1272
		} else {
1273
			memmove(c->prompt_buffer + c->prompt_index + n,
1274
			    c->prompt_buffer + c->prompt_index,
1275
			    (size + 1 - c->prompt_index) *
1276
			    sizeof *c->prompt_buffer);
1277
			for (idx = 0; idx < n; idx++) {
1278
				ud = &c->prompt_buffer[c->prompt_index + idx];
1279
				utf8_set(ud, bufdata[idx]);
1280
			}
1281
			c->prompt_index += n;
1282
		}
1283
		goto changed;
1284
	case '\024': /* C-t */
1285
		idx = c->prompt_index;
1286
		if (idx < size)
1287
			idx++;
1288
		if (idx >= 2) {
1289
			utf8_copy(&tmp, &c->prompt_buffer[idx - 2]);
1290
			utf8_copy(&c->prompt_buffer[idx - 2],
1291
			    &c->prompt_buffer[idx - 1]);
1292
			utf8_copy(&c->prompt_buffer[idx - 1], &tmp);
1293
			c->prompt_index = idx;
1294
			goto changed;
1295
		}
1296
		break;
1297
	case '\r':
1298
	case '\n':
1299
		s = utf8_tocstr(c->prompt_buffer);
1300
		if (*s != '\0')
1301
			status_prompt_add_history(s);
1302
		if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0)
1303
			status_prompt_clear(c);
1304
		free(s);
1305
		break;
1306
	case '\033': /* Escape */
1307
	case '\003': /* C-c */
1308
		if (c->prompt_inputcb(c, c->prompt_data, NULL, 1) == 0)
1309
			status_prompt_clear(c);
1310
		break;
1311
	case '\022': /* C-r */
1312
		if (c->prompt_flags & PROMPT_INCREMENTAL) {
1313
			prefix = '-';
1314
			goto changed;
1315
		}
1316
		break;
1317
	case '\023': /* C-s */
1318
		if (c->prompt_flags & PROMPT_INCREMENTAL) {
1319
			prefix = '+';
1320
			goto changed;
1321
		}
1322
		break;
1323
	default:
1324
		goto append_key;
1325
	}
1326
1327
	c->flags |= CLIENT_STATUS;
1328
	return (0);
1329
1330
append_key:
1331
	if (key <= 0x1f || key >= KEYC_BASE)
1332
		return (0);
1333
	if (utf8_split(key, &tmp) != UTF8_DONE)
1334
		return (0);
1335
1336
	c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 2,
1337
	    sizeof *c->prompt_buffer);
1338
1339
	if (c->prompt_index == size) {
1340
		utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp);
1341
		c->prompt_index++;
1342
		c->prompt_buffer[c->prompt_index].size = 0;
1343
	} else {
1344
		memmove(c->prompt_buffer + c->prompt_index + 1,
1345
		    c->prompt_buffer + c->prompt_index,
1346
		    (size + 1 - c->prompt_index) *
1347
		    sizeof *c->prompt_buffer);
1348
		utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp);
1349
		c->prompt_index++;
1350
	}
1351
1352
	if (c->prompt_flags & PROMPT_SINGLE) {
1353
		s = utf8_tocstr(c->prompt_buffer);
1354
		if (strlen(s) != 1)
1355
			status_prompt_clear(c);
1356
		else if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0)
1357
			status_prompt_clear(c);
1358
		free(s);
1359
	}
1360
1361
changed:
1362
	c->flags |= CLIENT_STATUS;
1363
	if (c->prompt_flags & PROMPT_INCREMENTAL) {
1364
		s = utf8_tocstr(c->prompt_buffer);
1365
		xasprintf(&cp, "%c%s", prefix, s);
1366
		c->prompt_inputcb(c, c->prompt_data, cp, 0);
1367
		free(cp);
1368
		free(s);
1369
	}
1370
	return (0);
1371
}
1372
1373
/* Get previous line from the history. */
1374
static const char *
1375
status_prompt_up_history(u_int *idx)
1376
{
1377
	/*
1378
	 * History runs from 0 to size - 1. Index is from 0 to size. Zero is
1379
	 * empty.
1380
	 */
1381
1382
	if (status_prompt_hsize == 0 || *idx == status_prompt_hsize)
1383
		return (NULL);
1384
	(*idx)++;
1385
	return (status_prompt_hlist[status_prompt_hsize - *idx]);
1386
}
1387
1388
/* Get next line from the history. */
1389
static const char *
1390
status_prompt_down_history(u_int *idx)
1391
{
1392
	if (status_prompt_hsize == 0 || *idx == 0)
1393
		return ("");
1394
	(*idx)--;
1395
	if (*idx == 0)
1396
		return ("");
1397
	return (status_prompt_hlist[status_prompt_hsize - *idx]);
1398
}
1399
1400
/* Add line to the history. */
1401
static void
1402
status_prompt_add_history(const char *line)
1403
{
1404
	size_t	size;
1405
1406
	if (status_prompt_hsize > 0 &&
1407
	    strcmp(status_prompt_hlist[status_prompt_hsize - 1], line) == 0)
1408
		return;
1409
1410
	if (status_prompt_hsize == PROMPT_HISTORY) {
1411
		free(status_prompt_hlist[0]);
1412
1413
		size = (PROMPT_HISTORY - 1) * sizeof *status_prompt_hlist;
1414
		memmove(&status_prompt_hlist[0], &status_prompt_hlist[1], size);
1415
1416
		status_prompt_hlist[status_prompt_hsize - 1] = xstrdup(line);
1417
		return;
1418
	}
1419
1420
	status_prompt_hlist = xreallocarray(status_prompt_hlist,
1421
	    status_prompt_hsize + 1, sizeof *status_prompt_hlist);
1422
	status_prompt_hlist[status_prompt_hsize++] = xstrdup(line);
1423
}
1424
1425
/* Build completion list. */
1426
static const char **
1427
status_prompt_complete_list(u_int *size, const char *s)
1428
{
1429
	const char				**list = NULL, **layout;
1430
	const struct cmd_entry			**cmdent;
1431
	const struct options_table_entry	 *oe;
1432
	const char				 *layouts[] = {
1433
		"even-horizontal", "even-vertical", "main-horizontal",
1434
		"main-vertical", "tiled", NULL
1435
	};
1436
1437
	*size = 0;
1438
	for (cmdent = cmd_table; *cmdent != NULL; cmdent++) {
1439
		if (strncmp((*cmdent)->name, s, strlen(s)) == 0) {
1440
			list = xreallocarray(list, (*size) + 1, sizeof *list);
1441
			list[(*size)++] = (*cmdent)->name;
1442
		}
1443
	}
1444
	for (oe = options_table; oe->name != NULL; oe++) {
1445
		if (strncmp(oe->name, s, strlen(s)) == 0) {
1446
			list = xreallocarray(list, (*size) + 1, sizeof *list);
1447
			list[(*size)++] = oe->name;
1448
		}
1449
	}
1450
	for (layout = layouts; *layout != NULL; layout++) {
1451
		if (strncmp(*layout, s, strlen(s)) == 0) {
1452
			list = xreallocarray(list, (*size) + 1, sizeof *list);
1453
			list[(*size)++] = *layout;
1454
		}
1455
	}
1456
	return (list);
1457
}
1458
1459
/* Find longest prefix. */
1460
static char *
1461
status_prompt_complete_prefix(const char **list, u_int size)
1462
{
1463
	char	 *out;
1464
	u_int	  i;
1465
	size_t	  j;
1466
1467
	out = xstrdup(list[0]);
1468
	for (i = 1; i < size; i++) {
1469
		j = strlen(list[i]);
1470
		if (j > strlen(out))
1471
			j = strlen(out);
1472
		for (; j > 0; j--) {
1473
			if (out[j - 1] != list[i][j - 1])
1474
				out[j - 1] = '\0';
1475
		}
1476
	}
1477
	return (out);
1478
}
1479
1480
/* Complete word. */
1481
static char *
1482
status_prompt_complete(struct session *session, const char *s)
1483
{
1484
	const char	**list = NULL, *colon;
1485
	u_int		  size = 0, i;
1486
	struct session	 *s_loop;
1487
	struct winlink	 *wl;
1488
	struct window	 *w;
1489
	char		 *copy, *out, *tmp;
1490
1491
	if (*s == '\0')
1492
		return (NULL);
1493
	out = NULL;
1494
1495
	if (strncmp(s, "-t", 2) != 0 && strncmp(s, "-s", 2) != 0) {
1496
		list = status_prompt_complete_list(&size, s);
1497
		if (size == 0)
1498
			out = NULL;
1499
		else if (size == 1)
1500
			xasprintf(&out, "%s ", list[0]);
1501
		else
1502
			out = status_prompt_complete_prefix(list, size);
1503
		free(list);
1504
		return (out);
1505
	}
1506
	copy = xstrdup(s);
1507
1508
	colon = ":";
1509
	if (copy[strlen(copy) - 1] == ':')
1510
		copy[strlen(copy) - 1] = '\0';
1511
	else
1512
		colon = "";
1513
	s = copy + 2;
1514
1515
	RB_FOREACH(s_loop, sessions, &sessions) {
1516
		if (strncmp(s_loop->name, s, strlen(s)) == 0) {
1517
			list = xreallocarray(list, size + 2, sizeof *list);
1518
			list[size++] = s_loop->name;
1519
		}
1520
	}
1521
	if (size == 1) {
1522
		out = xstrdup(list[0]);
1523
		if (session_find(list[0]) != NULL)
1524
			colon = ":";
1525
	} else if (size != 0)
1526
		out = status_prompt_complete_prefix(list, size);
1527
	if (out != NULL) {
1528
		xasprintf(&tmp, "-%c%s%s", copy[1], out, colon);
1529
		free(out);
1530
		out = tmp;
1531
		goto found;
1532
	}
1533
1534
	colon = "";
1535
	if (*s == ':') {
1536
		RB_FOREACH(wl, winlinks, &session->windows) {
1537
			xasprintf(&tmp, ":%s", wl->window->name);
1538
			if (strncmp(tmp, s, strlen(s)) == 0){
1539
				list = xreallocarray(list, size + 1,
1540
				    sizeof *list);
1541
				list[size++] = tmp;
1542
				continue;
1543
			}
1544
			free(tmp);
1545
1546
			xasprintf(&tmp, ":%d", wl->idx);
1547
			if (strncmp(tmp, s, strlen(s)) == 0) {
1548
				list = xreallocarray(list, size + 1,
1549
				    sizeof *list);
1550
				list[size++] = tmp;
1551
				continue;
1552
			}
1553
			free(tmp);
1554
		}
1555
	} else {
1556
		RB_FOREACH(s_loop, sessions, &sessions) {
1557
			RB_FOREACH(wl, winlinks, &s_loop->windows) {
1558
				w = wl->window;
1559
1560
				xasprintf(&tmp, "%s:%s", s_loop->name, w->name);
1561
				if (strncmp(tmp, s, strlen(s)) == 0) {
1562
					list = xreallocarray(list, size + 1,
1563
					    sizeof *list);
1564
					list[size++] = tmp;
1565
					continue;
1566
				}
1567
				free(tmp);
1568
1569
				xasprintf(&tmp, "%s:%d", s_loop->name, wl->idx);
1570
				if (strncmp(tmp, s, strlen(s)) == 0) {
1571
					list = xreallocarray(list, size + 1,
1572
					    sizeof *list);
1573
					list[size++] = tmp;
1574
					continue;
1575
				}
1576
				free(tmp);
1577
			}
1578
		}
1579
	}
1580
	if (size == 1) {
1581
		out = xstrdup(list[0]);
1582
		colon = " ";
1583
	} else if (size != 0)
1584
		out = status_prompt_complete_prefix(list, size);
1585
	if (out != NULL) {
1586
		xasprintf(&tmp, "-%c%s%s", copy[1], out, colon);
1587
		out = tmp;
1588
	}
1589
1590
	for (i = 0; i < size; i++)
1591
		free((void *)list[i]);
1592
1593
found:
1594
	free(copy);
1595
	free(list);
1596
	return (out);
1597
}