"Fossies" - the Fresh Open Source Software Archive

Member "tmux-3.2a/status.c" (10 Jun 2021, 42660 Bytes) of package /linux/misc/tmux-3.2a.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "status.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 3.2_vs_3.2a.

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