status.c revision 1.165
1/* $OpenBSD: status.c,v 1.165 2017/05/03 05:53:34 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 32static char *status_redraw_get_left(struct client *, time_t, 33 struct grid_cell *, size_t *); 34static char *status_redraw_get_right(struct client *, time_t, 35 struct grid_cell *, size_t *); 36static char *status_print(struct client *, struct winlink *, time_t, 37 struct grid_cell *); 38static char *status_replace(struct client *, struct winlink *, const char *, 39 time_t); 40static void status_message_callback(int, short, void *); 41static void status_timer_callback(int, short, void *); 42 43static char *status_prompt_find_history_file(void); 44static const char *status_prompt_up_history(u_int *); 45static const char *status_prompt_down_history(u_int *); 46static void status_prompt_add_history(const char *); 47 48static const char **status_prompt_complete_list(u_int *, const char *); 49static char *status_prompt_complete_prefix(const char **, u_int); 50static char *status_prompt_complete(struct session *, const char *); 51 52/* Status prompt history. */ 53#define PROMPT_HISTORY 100 54static char **status_prompt_hlist; 55static u_int status_prompt_hsize; 56 57/* Find the history file to load/save from/to. */ 58static char * 59status_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. */ 79void 80status_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. */ 119void 120status_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. */ 147static void 148status_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. */ 171void 172status_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. */ 186void 187status_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. */ 196void 197status_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. */ 208int 209status_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. */ 219static char * 220status_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. */ 241static char * 242status_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. */ 263struct window * 264status_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. */ 287int 288status_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 /* No status line? */ 305 if (c->tty.sy == 0 || !options_get_number(s->options, "status")) 306 return (1); 307 left = right = NULL; 308 larrow = rarrow = 0; 309 310 /* Store current time. */ 311 t = time(NULL); 312 313 /* Set up default colour. */ 314 style_apply(&stdgc, s->options, "status-style"); 315 316 /* Create the target screen. */ 317 memcpy(&old_status, &c->status, sizeof old_status); 318 screen_init(&c->status, c->tty.sx, 1, 0); 319 screen_write_start(&ctx, NULL, &c->status); 320 for (offset = 0; offset < c->tty.sx; offset++) 321 screen_write_putc(&ctx, &stdgc, ' '); 322 screen_write_stop(&ctx); 323 324 /* If the height is one line, blank status line. */ 325 if (c->tty.sy <= 1) 326 goto out; 327 328 /* Work out left and right strings. */ 329 memcpy(&lgc, &stdgc, sizeof lgc); 330 left = status_redraw_get_left(c, t, &lgc, &llen); 331 memcpy(&rgc, &stdgc, sizeof rgc); 332 right = status_redraw_get_right(c, t, &rgc, &rlen); 333 334 /* 335 * Figure out how much space we have for the window list. If there 336 * isn't enough space, just show a blank status line. 337 */ 338 needed = 0; 339 if (llen != 0) 340 needed += llen; 341 if (rlen != 0) 342 needed += rlen; 343 if (c->tty.sx == 0 || c->tty.sx <= needed) 344 goto out; 345 wlavailable = c->tty.sx - needed; 346 347 /* Calculate the total size needed for the window list. */ 348 wlstart = wloffset = wlwidth = 0; 349 RB_FOREACH(wl, winlinks, &s->windows) { 350 free(wl->status_text); 351 memcpy(&wl->status_cell, &stdgc, sizeof wl->status_cell); 352 wl->status_text = status_print(c, wl, t, &wl->status_cell); 353 wl->status_width = screen_write_cstrlen("%s", wl->status_text); 354 355 if (wl == s->curw) 356 wloffset = wlwidth; 357 358 oo = wl->window->options; 359 sep = options_get_string(oo, "window-status-separator"); 360 seplen = screen_write_cstrlen("%s", sep); 361 wlwidth += wl->status_width + seplen; 362 } 363 364 /* Create a new screen for the window list. */ 365 screen_init(&window_list, wlwidth, 1, 0); 366 367 /* And draw the window list into it. */ 368 screen_write_start(&ctx, NULL, &window_list); 369 RB_FOREACH(wl, winlinks, &s->windows) { 370 screen_write_cnputs(&ctx, -1, &wl->status_cell, "%s", 371 wl->status_text); 372 373 oo = wl->window->options; 374 sep = options_get_string(oo, "window-status-separator"); 375 screen_write_cnputs(&ctx, -1, &stdgc, "%s", sep); 376 } 377 screen_write_stop(&ctx); 378 379 /* If there is enough space for the total width, skip to draw now. */ 380 if (wlwidth <= wlavailable) 381 goto draw; 382 383 /* Find size of current window text. */ 384 wlsize = s->curw->status_width; 385 386 /* 387 * If the current window is already on screen, good to draw from the 388 * start and just leave off the end. 389 */ 390 if (wloffset + wlsize < wlavailable) { 391 if (wlavailable > 0) { 392 rarrow = 1; 393 wlavailable--; 394 } 395 wlwidth = wlavailable; 396 } else { 397 /* 398 * Work out how many characters we need to omit from the 399 * start. There are wlavailable characters to fill, and 400 * wloffset + wlsize must be the last. So, the start character 401 * is wloffset + wlsize - wlavailable. 402 */ 403 if (wlavailable > 0) { 404 larrow = 1; 405 wlavailable--; 406 } 407 408 wlstart = wloffset + wlsize - wlavailable; 409 if (wlavailable > 0 && wlwidth > wlstart + wlavailable + 1) { 410 rarrow = 1; 411 wlstart++; 412 wlavailable--; 413 } 414 wlwidth = wlavailable; 415 } 416 417 /* Bail if anything is now too small too. */ 418 if (wlwidth == 0 || wlavailable == 0) { 419 screen_free(&window_list); 420 goto out; 421 } 422 423 /* 424 * Now the start position is known, work out the state of the left and 425 * right arrows. 426 */ 427 offset = 0; 428 RB_FOREACH(wl, winlinks, &s->windows) { 429 if (wl->flags & WINLINK_ALERTFLAGS && 430 larrow == 1 && offset < wlstart) 431 larrow = -1; 432 433 offset += wl->status_width; 434 435 if (wl->flags & WINLINK_ALERTFLAGS && 436 rarrow == 1 && offset > wlstart + wlwidth) 437 rarrow = -1; 438 } 439 440draw: 441 /* Begin drawing. */ 442 screen_write_start(&ctx, NULL, &c->status); 443 444 /* Draw the left string and arrow. */ 445 screen_write_cursormove(&ctx, 0, 0); 446 if (llen != 0) 447 screen_write_cnputs(&ctx, llen, &lgc, "%s", left); 448 if (larrow != 0) { 449 memcpy(&gc, &stdgc, sizeof gc); 450 if (larrow == -1) 451 gc.attr ^= GRID_ATTR_REVERSE; 452 screen_write_putc(&ctx, &gc, '<'); 453 } 454 455 /* Draw the right string and arrow. */ 456 if (rarrow != 0) { 457 screen_write_cursormove(&ctx, c->tty.sx - rlen - 1, 0); 458 memcpy(&gc, &stdgc, sizeof gc); 459 if (rarrow == -1) 460 gc.attr ^= GRID_ATTR_REVERSE; 461 screen_write_putc(&ctx, &gc, '>'); 462 } else 463 screen_write_cursormove(&ctx, c->tty.sx - rlen, 0); 464 if (rlen != 0) 465 screen_write_cnputs(&ctx, rlen, &rgc, "%s", right); 466 467 /* Figure out the offset for the window list. */ 468 if (llen != 0) 469 wloffset = llen; 470 else 471 wloffset = 0; 472 if (wlwidth < wlavailable) { 473 switch (options_get_number(s->options, "status-justify")) { 474 case 1: /* centred */ 475 wloffset += (wlavailable - wlwidth) / 2; 476 break; 477 case 2: /* right */ 478 wloffset += (wlavailable - wlwidth); 479 break; 480 } 481 } 482 if (larrow != 0) 483 wloffset++; 484 485 /* Copy the window list. */ 486 c->wlmouse = -wloffset + wlstart; 487 screen_write_cursormove(&ctx, wloffset, 0); 488 screen_write_copy(&ctx, &window_list, wlstart, 0, wlwidth, 1, NULL, 489 NULL); 490 screen_free(&window_list); 491 492 screen_write_stop(&ctx); 493 494out: 495 free(left); 496 free(right); 497 498 if (grid_compare(c->status.grid, old_status.grid) == 0) { 499 screen_free(&old_status); 500 return (0); 501 } 502 screen_free(&old_status); 503 return (1); 504} 505 506/* Replace special sequences in fmt. */ 507static char * 508status_replace(struct client *c, struct winlink *wl, const char *fmt, time_t t) 509{ 510 struct format_tree *ft; 511 char *expanded; 512 u_int tag; 513 514 if (fmt == NULL) 515 return (xstrdup("")); 516 517 if (wl != NULL) 518 tag = FORMAT_WINDOW|wl->window->id; 519 else 520 tag = FORMAT_NONE; 521 if (c->flags & CLIENT_STATUSFORCE) 522 ft = format_create(c, NULL, tag, FORMAT_STATUS|FORMAT_FORCE); 523 else 524 ft = format_create(c, NULL, tag, FORMAT_STATUS); 525 format_defaults(ft, c, NULL, wl, NULL); 526 527 expanded = format_expand_time(ft, fmt, t); 528 529 format_free(ft); 530 return (expanded); 531} 532 533/* Return winlink status line entry and adjust gc as necessary. */ 534static char * 535status_print(struct client *c, struct winlink *wl, time_t t, 536 struct grid_cell *gc) 537{ 538 struct options *oo = wl->window->options; 539 struct session *s = c->session; 540 const char *fmt; 541 char *text; 542 543 style_apply_update(gc, oo, "window-status-style"); 544 fmt = options_get_string(oo, "window-status-format"); 545 if (wl == s->curw) { 546 style_apply_update(gc, oo, "window-status-current-style"); 547 fmt = options_get_string(oo, "window-status-current-format"); 548 } 549 if (wl == TAILQ_FIRST(&s->lastw)) 550 style_apply_update(gc, oo, "window-status-last-style"); 551 552 if (wl->flags & WINLINK_BELL) 553 style_apply_update(gc, oo, "window-status-bell-style"); 554 else if (wl->flags & (WINLINK_ACTIVITY|WINLINK_SILENCE)) 555 style_apply_update(gc, oo, "window-status-activity-style"); 556 557 text = status_replace(c, wl, fmt, t); 558 return (text); 559} 560 561/* Set a status line message. */ 562void 563status_message_set(struct client *c, const char *fmt, ...) 564{ 565 struct timeval tv; 566 va_list ap; 567 int delay; 568 569 status_message_clear(c); 570 571 va_start(ap, fmt); 572 xvasprintf(&c->message_string, fmt, ap); 573 va_end(ap); 574 575 server_client_add_message(c, "%s", c->message_string); 576 577 delay = options_get_number(c->session->options, "display-time"); 578 if (delay > 0) { 579 tv.tv_sec = delay / 1000; 580 tv.tv_usec = (delay % 1000) * 1000L; 581 582 if (event_initialized(&c->message_timer)) 583 evtimer_del(&c->message_timer); 584 evtimer_set(&c->message_timer, status_message_callback, c); 585 evtimer_add(&c->message_timer, &tv); 586 } 587 588 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE); 589 c->flags |= CLIENT_STATUS; 590} 591 592/* Clear status line message. */ 593void 594status_message_clear(struct client *c) 595{ 596 if (c->message_string == NULL) 597 return; 598 599 free(c->message_string); 600 c->message_string = NULL; 601 602 if (c->prompt_string == NULL) 603 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE); 604 c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */ 605 606 screen_reinit(&c->status); 607} 608 609/* Clear status line message after timer expires. */ 610static void 611status_message_callback(__unused int fd, __unused short event, void *data) 612{ 613 struct client *c = data; 614 615 status_message_clear(c); 616} 617 618/* Draw client message on status line of present else on last line. */ 619int 620status_message_redraw(struct client *c) 621{ 622 struct screen_write_ctx ctx; 623 struct session *s = c->session; 624 struct screen old_status; 625 size_t len; 626 struct grid_cell gc; 627 628 if (c->tty.sx == 0 || c->tty.sy == 0) 629 return (0); 630 memcpy(&old_status, &c->status, sizeof old_status); 631 screen_init(&c->status, c->tty.sx, 1, 0); 632 633 len = screen_write_strlen("%s", c->message_string); 634 if (len > c->tty.sx) 635 len = c->tty.sx; 636 637 style_apply(&gc, s->options, "message-style"); 638 639 screen_write_start(&ctx, NULL, &c->status); 640 641 screen_write_cursormove(&ctx, 0, 0); 642 screen_write_nputs(&ctx, len, &gc, "%s", c->message_string); 643 for (; len < c->tty.sx; len++) 644 screen_write_putc(&ctx, &gc, ' '); 645 646 screen_write_stop(&ctx); 647 648 if (grid_compare(c->status.grid, old_status.grid) == 0) { 649 screen_free(&old_status); 650 return (0); 651 } 652 screen_free(&old_status); 653 return (1); 654} 655 656/* Enable status line prompt. */ 657void 658status_prompt_set(struct client *c, const char *msg, const char *input, 659 int (*callbackfn)(void *, const char *, int), void (*freefn)(void *), 660 void *data, int flags) 661{ 662 struct format_tree *ft; 663 time_t t; 664 char *tmp, *cp; 665 666 ft = format_create(c, NULL, FORMAT_NONE, 0); 667 format_defaults(ft, c, NULL, NULL, NULL); 668 669 t = time(NULL); 670 tmp = format_expand_time(ft, input, t); 671 672 status_message_clear(c); 673 status_prompt_clear(c); 674 675 c->prompt_string = format_expand_time(ft, msg, t); 676 677 c->prompt_buffer = utf8_fromcstr(tmp); 678 c->prompt_index = utf8_strlen(c->prompt_buffer); 679 680 c->prompt_callbackfn = callbackfn; 681 c->prompt_freefn = freefn; 682 c->prompt_data = data; 683 684 c->prompt_hindex = 0; 685 686 c->prompt_flags = flags; 687 c->prompt_mode = PROMPT_ENTRY; 688 689 if (~flags & PROMPT_INCREMENTAL) 690 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE); 691 c->flags |= CLIENT_STATUS; 692 693 if ((flags & PROMPT_INCREMENTAL) && *tmp != '\0') { 694 xasprintf(&cp, "=%s", tmp); 695 c->prompt_callbackfn(c->prompt_data, cp, 0); 696 free(cp); 697 } 698 699 free(tmp); 700 format_free(ft); 701} 702 703/* Remove status line prompt. */ 704void 705status_prompt_clear(struct client *c) 706{ 707 if (c->prompt_string == NULL) 708 return; 709 710 if (c->prompt_freefn != NULL && c->prompt_data != NULL) 711 c->prompt_freefn(c->prompt_data); 712 713 free(c->prompt_string); 714 c->prompt_string = NULL; 715 716 free(c->prompt_buffer); 717 c->prompt_buffer = NULL; 718 719 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE); 720 c->flags |= CLIENT_REDRAW; /* screen was frozen and may have changed */ 721 722 screen_reinit(&c->status); 723} 724 725/* Update status line prompt with a new prompt string. */ 726void 727status_prompt_update(struct client *c, const char *msg, const char *input) 728{ 729 struct format_tree *ft; 730 time_t t; 731 char *tmp; 732 733 ft = format_create(c, NULL, FORMAT_NONE, 0); 734 format_defaults(ft, c, NULL, NULL, NULL); 735 736 t = time(NULL); 737 tmp = format_expand_time(ft, input, t); 738 739 free(c->prompt_string); 740 c->prompt_string = format_expand_time(ft, msg, t); 741 742 free(c->prompt_buffer); 743 c->prompt_buffer = utf8_fromcstr(tmp); 744 c->prompt_index = utf8_strlen(c->prompt_buffer); 745 746 c->prompt_hindex = 0; 747 748 c->flags |= CLIENT_STATUS; 749 750 free(tmp); 751 format_free(ft); 752} 753 754/* Draw client prompt on status line of present else on last line. */ 755int 756status_prompt_redraw(struct client *c) 757{ 758 struct screen_write_ctx ctx; 759 struct session *s = c->session; 760 struct screen old_status; 761 u_int i, offset, left, start, pcursor, pwidth, width; 762 struct grid_cell gc, cursorgc; 763 764 if (c->tty.sx == 0 || c->tty.sy == 0) 765 return (0); 766 memcpy(&old_status, &c->status, sizeof old_status); 767 screen_init(&c->status, c->tty.sx, 1, 0); 768 769 if (c->prompt_mode == PROMPT_COMMAND) 770 style_apply(&gc, s->options, "message-command-style"); 771 else 772 style_apply(&gc, s->options, "message-style"); 773 774 memcpy(&cursorgc, &gc, sizeof cursorgc); 775 cursorgc.attr ^= GRID_ATTR_REVERSE; 776 777 start = screen_write_strlen("%s", c->prompt_string); 778 if (start > c->tty.sx) 779 start = c->tty.sx; 780 781 screen_write_start(&ctx, NULL, &c->status); 782 screen_write_cursormove(&ctx, 0, 0); 783 screen_write_nputs(&ctx, start, &gc, "%s", c->prompt_string); 784 while (c->status.cx < screen_size_x(&c->status)) 785 screen_write_putc(&ctx, &gc, ' '); 786 screen_write_cursormove(&ctx, start, 0); 787 788 left = c->tty.sx - start; 789 if (left == 0) 790 goto finished; 791 792 pcursor = utf8_strwidth(c->prompt_buffer, c->prompt_index); 793 pwidth = utf8_strwidth(c->prompt_buffer, -1); 794 if (pcursor >= left) { 795 /* 796 * The cursor would be outside the screen so start drawing 797 * with it on the right. 798 */ 799 offset = (pcursor - left) + 1; 800 pwidth = left; 801 } else 802 offset = 0; 803 if (pwidth > left) 804 pwidth = left; 805 806 width = 0; 807 for (i = 0; c->prompt_buffer[i].size != 0; i++) { 808 if (width < offset) { 809 width += c->prompt_buffer[i].width; 810 continue; 811 } 812 if (width >= offset + pwidth) 813 break; 814 width += c->prompt_buffer[i].width; 815 if (width > offset + pwidth) 816 break; 817 818 if (i != c->prompt_index) { 819 utf8_copy(&gc.data, &c->prompt_buffer[i]); 820 screen_write_cell(&ctx, &gc); 821 } else { 822 utf8_copy(&cursorgc.data, &c->prompt_buffer[i]); 823 screen_write_cell(&ctx, &cursorgc); 824 } 825 } 826 if (c->status.cx < screen_size_x(&c->status) && c->prompt_index >= i) 827 screen_write_putc(&ctx, &cursorgc, ' '); 828 829finished: 830 screen_write_stop(&ctx); 831 832 if (grid_compare(c->status.grid, old_status.grid) == 0) { 833 screen_free(&old_status); 834 return (0); 835 } 836 screen_free(&old_status); 837 return (1); 838} 839 840/* Is this a separator? */ 841static int 842status_prompt_in_list(const char *ws, const struct utf8_data *ud) 843{ 844 if (ud->size != 1 || ud->width != 1) 845 return (0); 846 return (strchr(ws, *ud->data) != NULL); 847} 848 849/* Is this a space? */ 850static int 851status_prompt_space(const struct utf8_data *ud) 852{ 853 if (ud->size != 1 || ud->width != 1) 854 return (0); 855 return (*ud->data == ' '); 856} 857 858/* 859 * Translate key from emacs to vi. Return 0 to drop key, 1 to process the key 860 * as an emacs key; return 2 to append to the buffer. 861 */ 862static int 863status_prompt_translate_key(struct client *c, key_code key, key_code *new_key) 864{ 865 if (c->prompt_mode == PROMPT_ENTRY) { 866 switch (key) { 867 case '\003': /* C-c */ 868 case '\010': /* C-h */ 869 case '\011': /* Tab */ 870 case '\025': /* C-u */ 871 case '\027': /* C-w */ 872 case '\n': 873 case '\r': 874 case KEYC_BSPACE: 875 case KEYC_DC: 876 case KEYC_DOWN: 877 case KEYC_END: 878 case KEYC_HOME: 879 case KEYC_LEFT: 880 case KEYC_RIGHT: 881 case KEYC_UP: 882 *new_key = key; 883 return (1); 884 case '\033': /* Escape */ 885 c->prompt_mode = PROMPT_COMMAND; 886 c->flags |= CLIENT_STATUS; 887 return (0); 888 } 889 *new_key = key; 890 return (2); 891 } 892 893 switch (key) { 894 case 'A': 895 case 'I': 896 case 'C': 897 case 's': 898 case 'a': 899 c->prompt_mode = PROMPT_ENTRY; 900 c->flags |= CLIENT_STATUS; 901 break; /* switch mode and... */ 902 case 'S': 903 c->prompt_mode = PROMPT_ENTRY; 904 c->flags |= CLIENT_STATUS; 905 *new_key = '\025'; /* C-u */ 906 return (1); 907 case 'i': 908 case '\033': /* Escape */ 909 c->prompt_mode = PROMPT_ENTRY; 910 c->flags |= CLIENT_STATUS; 911 return (0); 912 } 913 914 switch (key) { 915 case 'A': 916 case '$': 917 *new_key = KEYC_END; 918 return (1); 919 case 'I': 920 case '0': 921 case '^': 922 *new_key = KEYC_HOME; 923 return (1); 924 case 'C': 925 case 'D': 926 *new_key = '\013'; /* C-k */ 927 return (1); 928 case KEYC_BSPACE: 929 case 'X': 930 *new_key = KEYC_BSPACE; 931 return (1); 932 case 'b': 933 case 'B': 934 *new_key = 'b'|KEYC_ESCAPE; 935 return (1); 936 case 'd': 937 *new_key = '\025'; 938 return (1); 939 case 'e': 940 case 'E': 941 case 'w': 942 case 'W': 943 *new_key = 'f'|KEYC_ESCAPE; 944 return (1); 945 case 'p': 946 *new_key = '\031'; /* C-y */ 947 return (1); 948 case 's': 949 case KEYC_DC: 950 case 'x': 951 *new_key = KEYC_DC; 952 return (1); 953 case KEYC_DOWN: 954 case 'j': 955 *new_key = KEYC_DOWN; 956 return (1); 957 case KEYC_LEFT: 958 case 'h': 959 *new_key = KEYC_LEFT; 960 return (1); 961 case 'a': 962 case KEYC_RIGHT: 963 case 'l': 964 *new_key = KEYC_RIGHT; 965 return (1); 966 case KEYC_UP: 967 case 'k': 968 *new_key = KEYC_UP; 969 return (1); 970 case '\010' /* C-h */: 971 case '\003' /* C-c */: 972 case '\n': 973 case '\r': 974 return (1); 975 } 976 return (0); 977} 978 979/* Handle keys in prompt. */ 980int 981status_prompt_key(struct client *c, key_code key) 982{ 983 struct options *oo = c->session->options; 984 struct paste_buffer *pb; 985 char *s, *cp, word[64], prefix = '='; 986 const char *histstr, *bufdata, *ws = NULL; 987 u_char ch; 988 size_t size, n, off, idx, bufsize, used; 989 struct utf8_data tmp, *first, *last, *ud; 990 int keys; 991 992 size = utf8_strlen(c->prompt_buffer); 993 994 if (c->prompt_flags & PROMPT_NUMERIC) { 995 if (key >= '0' && key <= '9') 996 goto append_key; 997 s = utf8_tocstr(c->prompt_buffer); 998 c->prompt_callbackfn(c->prompt_data, s, 1); 999 status_prompt_clear(c); 1000 free(s); 1001 return (1); 1002 } 1003 1004 keys = options_get_number(c->session->options, "status-keys"); 1005 if (keys == MODEKEY_VI) { 1006 switch (status_prompt_translate_key(c, key, &key)) { 1007 case 1: 1008 goto process_key; 1009 case 2: 1010 goto append_key; 1011 default: 1012 return (0); 1013 } 1014 } 1015 1016process_key: 1017 switch (key) { 1018 case KEYC_LEFT: 1019 case '\002': /* C-b */ 1020 if (c->prompt_index > 0) { 1021 c->prompt_index--; 1022 break; 1023 } 1024 break; 1025 case KEYC_RIGHT: 1026 case '\006': /* C-f */ 1027 if (c->prompt_index < size) { 1028 c->prompt_index++; 1029 break; 1030 } 1031 break; 1032 case KEYC_HOME: 1033 case '\001': /* C-a */ 1034 if (c->prompt_index != 0) { 1035 c->prompt_index = 0; 1036 break; 1037 } 1038 break; 1039 case KEYC_END: 1040 case '\005': /* C-e */ 1041 if (c->prompt_index != size) { 1042 c->prompt_index = size; 1043 break; 1044 } 1045 break; 1046 case '\011': /* Tab */ 1047 if (c->prompt_buffer[0].size == 0) 1048 break; 1049 1050 idx = c->prompt_index; 1051 if (idx != 0) 1052 idx--; 1053 1054 /* Find the word we are in. */ 1055 first = &c->prompt_buffer[idx]; 1056 while (first > c->prompt_buffer && !status_prompt_space(first)) 1057 first--; 1058 while (first->size != 0 && status_prompt_space(first)) 1059 first++; 1060 last = &c->prompt_buffer[idx]; 1061 while (last->size != 0 && !status_prompt_space(last)) 1062 last++; 1063 while (last > c->prompt_buffer && status_prompt_space(last)) 1064 last--; 1065 if (last->size != 0) 1066 last++; 1067 if (last <= first) 1068 break; 1069 1070 used = 0; 1071 for (ud = first; ud < last; ud++) { 1072 if (used + ud->size >= sizeof word) 1073 break; 1074 memcpy(word + used, ud->data, ud->size); 1075 used += ud->size; 1076 } 1077 if (ud != last) 1078 break; 1079 word[used] = '\0'; 1080 1081 /* And try to complete it. */ 1082 if ((s = status_prompt_complete(c->session, word)) == NULL) 1083 break; 1084 1085 /* Trim out word. */ 1086 n = size - (last - c->prompt_buffer) + 1; /* with \0 */ 1087 memmove(first, last, n * sizeof *c->prompt_buffer); 1088 size -= last - first; 1089 1090 /* Insert the new word. */ 1091 size += strlen(s); 1092 off = first - c->prompt_buffer; 1093 c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 1, 1094 sizeof *c->prompt_buffer); 1095 first = c->prompt_buffer + off; 1096 memmove(first + strlen(s), first, n * sizeof *c->prompt_buffer); 1097 for (idx = 0; idx < strlen(s); idx++) 1098 utf8_set(&first[idx], s[idx]); 1099 1100 c->prompt_index = (first - c->prompt_buffer) + strlen(s); 1101 free(s); 1102 1103 goto changed; 1104 case KEYC_BSPACE: 1105 case '\010': /* C-h */ 1106 if (c->prompt_index != 0) { 1107 if (c->prompt_index == size) 1108 c->prompt_buffer[--c->prompt_index].size = 0; 1109 else { 1110 memmove(c->prompt_buffer + c->prompt_index - 1, 1111 c->prompt_buffer + c->prompt_index, 1112 (size + 1 - c->prompt_index) * 1113 sizeof *c->prompt_buffer); 1114 c->prompt_index--; 1115 } 1116 goto changed; 1117 } 1118 break; 1119 case KEYC_DC: 1120 case '\004': /* C-d */ 1121 if (c->prompt_index != size) { 1122 memmove(c->prompt_buffer + c->prompt_index, 1123 c->prompt_buffer + c->prompt_index + 1, 1124 (size + 1 - c->prompt_index) * 1125 sizeof *c->prompt_buffer); 1126 goto changed; 1127 } 1128 break; 1129 case '\025': /* C-u */ 1130 c->prompt_buffer[0].size = 0; 1131 c->prompt_index = 0; 1132 goto changed; 1133 case '\013': /* C-k */ 1134 if (c->prompt_index < size) { 1135 c->prompt_buffer[c->prompt_index].size = 0; 1136 goto changed; 1137 } 1138 break; 1139 case '\027': /* C-w */ 1140 ws = options_get_string(oo, "word-separators"); 1141 idx = c->prompt_index; 1142 1143 /* Find a non-separator. */ 1144 while (idx != 0) { 1145 idx--; 1146 if (!status_prompt_in_list(ws, &c->prompt_buffer[idx])) 1147 break; 1148 } 1149 1150 /* Find the separator at the beginning of the word. */ 1151 while (idx != 0) { 1152 idx--; 1153 if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) { 1154 /* Go back to the word. */ 1155 idx++; 1156 break; 1157 } 1158 } 1159 1160 memmove(c->prompt_buffer + idx, 1161 c->prompt_buffer + c->prompt_index, 1162 (size + 1 - c->prompt_index) * 1163 sizeof *c->prompt_buffer); 1164 memset(c->prompt_buffer + size - (c->prompt_index - idx), 1165 '\0', (c->prompt_index - idx) * sizeof *c->prompt_buffer); 1166 c->prompt_index = idx; 1167 1168 goto changed; 1169 case 'f'|KEYC_ESCAPE: 1170 ws = options_get_string(oo, "word-separators"); 1171 1172 /* Find a word. */ 1173 while (c->prompt_index != size) { 1174 idx = ++c->prompt_index; 1175 if (!status_prompt_in_list(ws, &c->prompt_buffer[idx])) 1176 break; 1177 } 1178 1179 /* Find the separator at the end of the word. */ 1180 while (c->prompt_index != size) { 1181 idx = ++c->prompt_index; 1182 if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) 1183 break; 1184 } 1185 1186 /* Back up to the end-of-word like vi. */ 1187 if (options_get_number(oo, "status-keys") == MODEKEY_VI && 1188 c->prompt_index != 0) 1189 c->prompt_index--; 1190 1191 goto changed; 1192 case 'b'|KEYC_ESCAPE: 1193 ws = options_get_string(oo, "word-separators"); 1194 1195 /* Find a non-separator. */ 1196 while (c->prompt_index != 0) { 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 beginning of the word. */ 1203 while (c->prompt_index != 0) { 1204 idx = --c->prompt_index; 1205 if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) { 1206 /* Go back to the word. */ 1207 c->prompt_index++; 1208 break; 1209 } 1210 } 1211 goto changed; 1212 case KEYC_UP: 1213 case '\020': /* C-p */ 1214 histstr = status_prompt_up_history(&c->prompt_hindex); 1215 if (histstr == NULL) 1216 break; 1217 free(c->prompt_buffer); 1218 c->prompt_buffer = utf8_fromcstr(histstr); 1219 c->prompt_index = utf8_strlen(c->prompt_buffer); 1220 goto changed; 1221 case KEYC_DOWN: 1222 case '\016': /* C-n */ 1223 histstr = status_prompt_down_history(&c->prompt_hindex); 1224 if (histstr == NULL) 1225 break; 1226 free(c->prompt_buffer); 1227 c->prompt_buffer = utf8_fromcstr(histstr); 1228 c->prompt_index = utf8_strlen(c->prompt_buffer); 1229 goto changed; 1230 case '\031': /* C-y */ 1231 if ((pb = paste_get_top(NULL)) == NULL) 1232 break; 1233 bufdata = paste_buffer_data(pb, &bufsize); 1234 for (n = 0; n < bufsize; n++) { 1235 ch = (u_char)bufdata[n]; 1236 if (ch < 32 || ch >= 127) 1237 break; 1238 } 1239 1240 c->prompt_buffer = xreallocarray(c->prompt_buffer, size + n + 1, 1241 sizeof *c->prompt_buffer); 1242 if (c->prompt_index == size) { 1243 for (idx = 0; idx < n; idx++) { 1244 ud = &c->prompt_buffer[c->prompt_index + idx]; 1245 utf8_set(ud, bufdata[idx]); 1246 } 1247 c->prompt_index += n; 1248 c->prompt_buffer[c->prompt_index].size = 0; 1249 } else { 1250 memmove(c->prompt_buffer + c->prompt_index + n, 1251 c->prompt_buffer + c->prompt_index, 1252 (size + 1 - c->prompt_index) * 1253 sizeof *c->prompt_buffer); 1254 for (idx = 0; idx < n; idx++) { 1255 ud = &c->prompt_buffer[c->prompt_index + idx]; 1256 utf8_set(ud, bufdata[idx]); 1257 } 1258 c->prompt_index += n; 1259 } 1260 goto changed; 1261 case '\024': /* C-t */ 1262 idx = c->prompt_index; 1263 if (idx < size) 1264 idx++; 1265 if (idx >= 2) { 1266 utf8_copy(&tmp, &c->prompt_buffer[idx - 2]); 1267 utf8_copy(&c->prompt_buffer[idx - 2], 1268 &c->prompt_buffer[idx - 1]); 1269 utf8_copy(&c->prompt_buffer[idx - 1], &tmp); 1270 c->prompt_index = idx; 1271 goto changed; 1272 } 1273 break; 1274 case '\r': 1275 case '\n': 1276 s = utf8_tocstr(c->prompt_buffer); 1277 if (*s != '\0') 1278 status_prompt_add_history(s); 1279 if (c->prompt_callbackfn(c->prompt_data, s, 1) == 0) 1280 status_prompt_clear(c); 1281 free(s); 1282 break; 1283 case '\033': /* Escape */ 1284 case '\003': /* C-c */ 1285 if (c->prompt_callbackfn(c->prompt_data, NULL, 1) == 0) 1286 status_prompt_clear(c); 1287 break; 1288 case '\022': /* C-r */ 1289 if (c->prompt_flags & PROMPT_INCREMENTAL) { 1290 prefix = '-'; 1291 goto changed; 1292 } 1293 break; 1294 case '\023': /* C-s */ 1295 if (c->prompt_flags & PROMPT_INCREMENTAL) { 1296 prefix = '+'; 1297 goto changed; 1298 } 1299 break; 1300 default: 1301 goto append_key; 1302 } 1303 1304 c->flags |= CLIENT_STATUS; 1305 return (0); 1306 1307append_key: 1308 if (key <= 0x1f || key >= KEYC_BASE) 1309 return (0); 1310 if (utf8_split(key, &tmp) != UTF8_DONE) 1311 return (0); 1312 1313 c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 2, 1314 sizeof *c->prompt_buffer); 1315 1316 if (c->prompt_index == size) { 1317 utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp); 1318 c->prompt_index++; 1319 c->prompt_buffer[c->prompt_index].size = 0; 1320 } else { 1321 memmove(c->prompt_buffer + c->prompt_index + 1, 1322 c->prompt_buffer + c->prompt_index, 1323 (size + 1 - c->prompt_index) * 1324 sizeof *c->prompt_buffer); 1325 utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp); 1326 c->prompt_index++; 1327 } 1328 1329 if (c->prompt_flags & PROMPT_SINGLE) { 1330 s = utf8_tocstr(c->prompt_buffer); 1331 if (strlen(s) != 1) 1332 status_prompt_clear(c); 1333 else if (c->prompt_callbackfn(c->prompt_data, s, 1) == 0) 1334 status_prompt_clear(c); 1335 free(s); 1336 } 1337 1338changed: 1339 c->flags |= CLIENT_STATUS; 1340 if (c->prompt_flags & PROMPT_INCREMENTAL) { 1341 s = utf8_tocstr(c->prompt_buffer); 1342 xasprintf(&cp, "%c%s", prefix, s); 1343 c->prompt_callbackfn(c->prompt_data, cp, 0); 1344 free(cp); 1345 free(s); 1346 } 1347 return (0); 1348} 1349 1350/* Get previous line from the history. */ 1351static const char * 1352status_prompt_up_history(u_int *idx) 1353{ 1354 /* 1355 * History runs from 0 to size - 1. Index is from 0 to size. Zero is 1356 * empty. 1357 */ 1358 1359 if (status_prompt_hsize == 0 || *idx == status_prompt_hsize) 1360 return (NULL); 1361 (*idx)++; 1362 return (status_prompt_hlist[status_prompt_hsize - *idx]); 1363} 1364 1365/* Get next line from the history. */ 1366static const char * 1367status_prompt_down_history(u_int *idx) 1368{ 1369 if (status_prompt_hsize == 0 || *idx == 0) 1370 return (""); 1371 (*idx)--; 1372 if (*idx == 0) 1373 return (""); 1374 return (status_prompt_hlist[status_prompt_hsize - *idx]); 1375} 1376 1377/* Add line to the history. */ 1378static void 1379status_prompt_add_history(const char *line) 1380{ 1381 size_t size; 1382 1383 if (status_prompt_hsize > 0 && 1384 strcmp(status_prompt_hlist[status_prompt_hsize - 1], line) == 0) 1385 return; 1386 1387 if (status_prompt_hsize == PROMPT_HISTORY) { 1388 free(status_prompt_hlist[0]); 1389 1390 size = (PROMPT_HISTORY - 1) * sizeof *status_prompt_hlist; 1391 memmove(&status_prompt_hlist[0], &status_prompt_hlist[1], size); 1392 1393 status_prompt_hlist[status_prompt_hsize - 1] = xstrdup(line); 1394 return; 1395 } 1396 1397 status_prompt_hlist = xreallocarray(status_prompt_hlist, 1398 status_prompt_hsize + 1, sizeof *status_prompt_hlist); 1399 status_prompt_hlist[status_prompt_hsize++] = xstrdup(line); 1400} 1401 1402/* Build completion list. */ 1403static const char ** 1404status_prompt_complete_list(u_int *size, const char *s) 1405{ 1406 const char **list = NULL, **layout; 1407 const struct cmd_entry **cmdent; 1408 const struct options_table_entry *oe; 1409 const char *layouts[] = { 1410 "even-horizontal", "even-vertical", "main-horizontal", 1411 "main-vertical", "tiled", NULL 1412 }; 1413 1414 *size = 0; 1415 for (cmdent = cmd_table; *cmdent != NULL; cmdent++) { 1416 if (strncmp((*cmdent)->name, s, strlen(s)) == 0) { 1417 list = xreallocarray(list, (*size) + 1, sizeof *list); 1418 list[(*size)++] = (*cmdent)->name; 1419 } 1420 } 1421 for (oe = options_table; oe->name != NULL; oe++) { 1422 if (strncmp(oe->name, s, strlen(s)) == 0) { 1423 list = xreallocarray(list, (*size) + 1, sizeof *list); 1424 list[(*size)++] = oe->name; 1425 } 1426 } 1427 for (layout = layouts; *layout != NULL; layout++) { 1428 if (strncmp(*layout, s, strlen(s)) == 0) { 1429 list = xreallocarray(list, (*size) + 1, sizeof *list); 1430 list[(*size)++] = *layout; 1431 } 1432 } 1433 return (list); 1434} 1435 1436/* Find longest prefix. */ 1437static char * 1438status_prompt_complete_prefix(const char **list, u_int size) 1439{ 1440 char *out; 1441 u_int i; 1442 size_t j; 1443 1444 out = xstrdup(list[0]); 1445 for (i = 1; i < size; i++) { 1446 j = strlen(list[i]); 1447 if (j > strlen(out)) 1448 j = strlen(out); 1449 for (; j > 0; j--) { 1450 if (out[j - 1] != list[i][j - 1]) 1451 out[j - 1] = '\0'; 1452 } 1453 } 1454 return (out); 1455} 1456 1457/* Complete word. */ 1458static char * 1459status_prompt_complete(struct session *session, const char *s) 1460{ 1461 const char **list = NULL, *colon; 1462 u_int size = 0, i; 1463 struct session *s_loop; 1464 struct winlink *wl; 1465 struct window *w; 1466 char *copy, *out, *tmp; 1467 1468 if (*s == '\0') 1469 return (NULL); 1470 out = NULL; 1471 1472 if (strncmp(s, "-t", 2) != 0 && strncmp(s, "-s", 2) != 0) { 1473 list = status_prompt_complete_list(&size, s); 1474 if (size == 0) 1475 out = NULL; 1476 else if (size == 1) 1477 xasprintf(&out, "%s ", list[0]); 1478 else 1479 out = status_prompt_complete_prefix(list, size); 1480 free(list); 1481 return (out); 1482 } 1483 copy = xstrdup(s); 1484 1485 colon = ":"; 1486 if (copy[strlen(copy) - 1] == ':') 1487 copy[strlen(copy) - 1] = '\0'; 1488 else 1489 colon = ""; 1490 s = copy + 2; 1491 1492 RB_FOREACH(s_loop, sessions, &sessions) { 1493 if (strncmp(s_loop->name, s, strlen(s)) == 0) { 1494 list = xreallocarray(list, size + 2, sizeof *list); 1495 list[size++] = s_loop->name; 1496 } 1497 } 1498 if (size == 1) { 1499 out = xstrdup(list[0]); 1500 if (session_find(list[0]) != NULL) 1501 colon = ":"; 1502 } else if (size != 0) 1503 out = status_prompt_complete_prefix(list, size); 1504 if (out != NULL) { 1505 xasprintf(&tmp, "-%c%s%s", copy[1], out, colon); 1506 free(out); 1507 out = tmp; 1508 goto found; 1509 } 1510 1511 colon = ""; 1512 if (*s == ':') { 1513 RB_FOREACH(wl, winlinks, &session->windows) { 1514 xasprintf(&tmp, ":%s", wl->window->name); 1515 if (strncmp(tmp, s, strlen(s)) == 0){ 1516 list = xreallocarray(list, size + 1, 1517 sizeof *list); 1518 list[size++] = tmp; 1519 continue; 1520 } 1521 free(tmp); 1522 1523 xasprintf(&tmp, ":%d", wl->idx); 1524 if (strncmp(tmp, s, strlen(s)) == 0) { 1525 list = xreallocarray(list, size + 1, 1526 sizeof *list); 1527 list[size++] = tmp; 1528 continue; 1529 } 1530 free(tmp); 1531 } 1532 } else { 1533 RB_FOREACH(s_loop, sessions, &sessions) { 1534 RB_FOREACH(wl, winlinks, &s_loop->windows) { 1535 w = wl->window; 1536 1537 xasprintf(&tmp, "%s:%s", s_loop->name, w->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, "%s:%d", s_loop->name, 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 } 1556 } 1557 if (size == 1) { 1558 out = xstrdup(list[0]); 1559 colon = " "; 1560 } else if (size != 0) 1561 out = status_prompt_complete_prefix(list, size); 1562 if (out != NULL) { 1563 xasprintf(&tmp, "-%c%s%s", copy[1], out, colon); 1564 out = tmp; 1565 } 1566 1567 for (i = 0; i < size; i++) 1568 free((void *)list[i]); 1569 1570found: 1571 free(copy); 1572 free(list); 1573 return (out); 1574} 1575