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