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