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