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