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