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