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