1 /* $OpenBSD: status.c,v 1.221 2021/04/12 09:36:12 nicm Exp $ */ 2 3 /* 4 * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 15 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 16 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <sys/types.h> 20 #include <sys/time.h> 21 22 #include <errno.h> 23 #include <limits.h> 24 #include <stdarg.h> 25 #include <stdlib.h> 26 #include <string.h> 27 #include <time.h> 28 #include <unistd.h> 29 30 #include "tmux.h" 31 32 static void status_message_callback(int, short, void *); 33 static void status_timer_callback(int, short, void *); 34 35 static char *status_prompt_find_history_file(void); 36 static const char *status_prompt_up_history(u_int *); 37 static const char *status_prompt_down_history(u_int *); 38 static void status_prompt_add_history(const char *); 39 40 static char *status_prompt_complete(struct client *, const char *, u_int); 41 static char *status_prompt_complete_window_menu(struct client *, 42 struct session *, const char *, u_int, char); 43 44 struct status_prompt_menu { 45 struct client *c; 46 u_int start; 47 u_int size; 48 char **list; 49 char flag; 50 }; 51 52 /* Status prompt history. */ 53 #define PROMPT_HISTORY 100 54 static char **status_prompt_hlist; 55 static u_int status_prompt_hsize; 56 57 /* Find the history file to load/save from/to. */ 58 static char * 59 status_prompt_find_history_file(void) 60 { 61 const char *home, *history_file; 62 char *path; 63 64 history_file = options_get_string(global_options, "history-file"); 65 if (*history_file == '\0') 66 return (NULL); 67 if (*history_file == '/') 68 return (xstrdup(history_file)); 69 70 if (history_file[0] != '~' || history_file[1] != '/') 71 return (NULL); 72 if ((home = find_home()) == NULL) 73 return (NULL); 74 xasprintf(&path, "%s%s", home, history_file + 1); 75 return (path); 76 } 77 78 /* Load status prompt history from file. */ 79 void 80 status_prompt_load_history(void) 81 { 82 FILE *f; 83 char *history_file, *line, *tmp; 84 size_t length; 85 86 if ((history_file = status_prompt_find_history_file()) == NULL) 87 return; 88 log_debug("loading history from %s", history_file); 89 90 f = fopen(history_file, "r"); 91 if (f == NULL) { 92 log_debug("%s: %s", history_file, strerror(errno)); 93 free(history_file); 94 return; 95 } 96 free(history_file); 97 98 for (;;) { 99 if ((line = fgetln(f, &length)) == NULL) 100 break; 101 102 if (length > 0) { 103 if (line[length - 1] == '\n') { 104 line[length - 1] = '\0'; 105 status_prompt_add_history(line); 106 } else { 107 tmp = xmalloc(length + 1); 108 memcpy(tmp, line, length); 109 tmp[length] = '\0'; 110 status_prompt_add_history(tmp); 111 free(tmp); 112 } 113 } 114 } 115 fclose(f); 116 } 117 118 /* Save status prompt history to file. */ 119 void 120 status_prompt_save_history(void) 121 { 122 FILE *f; 123 u_int i; 124 char *history_file; 125 126 if ((history_file = status_prompt_find_history_file()) == NULL) 127 return; 128 log_debug("saving history to %s", history_file); 129 130 f = fopen(history_file, "w"); 131 if (f == NULL) { 132 log_debug("%s: %s", history_file, strerror(errno)); 133 free(history_file); 134 return; 135 } 136 free(history_file); 137 138 for (i = 0; i < status_prompt_hsize; i++) { 139 fputs(status_prompt_hlist[i], f); 140 fputc('\n', f); 141 } 142 fclose(f); 143 144 } 145 146 /* Status timer callback. */ 147 static void 148 status_timer_callback(__unused int fd, __unused short events, void *arg) 149 { 150 struct client *c = arg; 151 struct session *s = c->session; 152 struct timeval tv; 153 154 evtimer_del(&c->status.timer); 155 156 if (s == NULL) 157 return; 158 159 if (c->message_string == NULL && c->prompt_string == NULL) 160 c->flags |= CLIENT_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. */ 171 void 172 status_timer_start(struct client *c) 173 { 174 struct session *s = c->session; 175 176 if (event_initialized(&c->status.timer)) 177 evtimer_del(&c->status.timer); 178 else 179 evtimer_set(&c->status.timer, status_timer_callback, c); 180 181 if (s != NULL && options_get_number(s->options, "status")) 182 status_timer_callback(-1, 0, c); 183 } 184 185 /* Start status timer for all clients. */ 186 void 187 status_timer_start_all(void) 188 { 189 struct client *c; 190 191 TAILQ_FOREACH(c, &clients, entry) 192 status_timer_start(c); 193 } 194 195 /* Update status cache. */ 196 void 197 status_update_cache(struct session *s) 198 { 199 s->statuslines = options_get_number(s->options, "status"); 200 if (s->statuslines == 0) 201 s->statusat = -1; 202 else if (options_get_number(s->options, "status-position") == 0) 203 s->statusat = 0; 204 else 205 s->statusat = 1; 206 } 207 208 /* Get screen line of status line. -1 means off. */ 209 int 210 status_at_line(struct client *c) 211 { 212 struct session *s = c->session; 213 214 if (c->flags & (CLIENT_STATUSOFF|CLIENT_CONTROL)) 215 return (-1); 216 if (s->statusat != 1) 217 return (s->statusat); 218 return (c->tty.sy - status_line_size(c)); 219 } 220 221 /* Get size of status line for client's session. 0 means off. */ 222 u_int 223 status_line_size(struct client *c) 224 { 225 struct session *s = c->session; 226 227 if (c->flags & (CLIENT_STATUSOFF|CLIENT_CONTROL)) 228 return (0); 229 return (s->statuslines); 230 } 231 232 /* Get window at window list position. */ 233 struct style_range * 234 status_get_range(struct client *c, u_int x, u_int y) 235 { 236 struct status_line *sl = &c->status; 237 struct style_range *sr; 238 239 if (y >= nitems(sl->entries)) 240 return (NULL); 241 TAILQ_FOREACH(sr, &sl->entries[y].ranges, entry) { 242 if (x >= sr->start && x < sr->end) 243 return (sr); 244 } 245 return (NULL); 246 } 247 248 /* Free all ranges. */ 249 static void 250 status_free_ranges(struct style_ranges *srs) 251 { 252 struct style_range *sr, *sr1; 253 254 TAILQ_FOREACH_SAFE(sr, srs, entry, sr1) { 255 TAILQ_REMOVE(srs, sr, entry); 256 free(sr); 257 } 258 } 259 260 /* Save old status line. */ 261 static void 262 status_push_screen(struct client *c) 263 { 264 struct status_line *sl = &c->status; 265 266 if (sl->active == &sl->screen) { 267 sl->active = xmalloc(sizeof *sl->active); 268 screen_init(sl->active, c->tty.sx, status_line_size(c), 0); 269 } 270 sl->references++; 271 } 272 273 /* Restore old status line. */ 274 static void 275 status_pop_screen(struct client *c) 276 { 277 struct status_line *sl = &c->status; 278 279 if (--sl->references == 0) { 280 screen_free(sl->active); 281 free(sl->active); 282 sl->active = &sl->screen; 283 } 284 } 285 286 /* Initialize status line. */ 287 void 288 status_init(struct client *c) 289 { 290 struct status_line *sl = &c->status; 291 u_int i; 292 293 for (i = 0; i < nitems(sl->entries); i++) 294 TAILQ_INIT(&sl->entries[i].ranges); 295 296 screen_init(&sl->screen, c->tty.sx, 1, 0); 297 sl->active = &sl->screen; 298 } 299 300 /* Free status line. */ 301 void 302 status_free(struct client *c) 303 { 304 struct status_line *sl = &c->status; 305 u_int i; 306 307 for (i = 0; i < nitems(sl->entries); i++) { 308 status_free_ranges(&sl->entries[i].ranges); 309 free((void *)sl->entries[i].expanded); 310 } 311 312 if (event_initialized(&sl->timer)) 313 evtimer_del(&sl->timer); 314 315 if (sl->active != &sl->screen) { 316 screen_free(sl->active); 317 free(sl->active); 318 } 319 screen_free(&sl->screen); 320 } 321 322 /* Draw status line for client. */ 323 int 324 status_redraw(struct client *c) 325 { 326 struct status_line *sl = &c->status; 327 struct status_line_entry *sle; 328 struct session *s = c->session; 329 struct screen_write_ctx ctx; 330 struct grid_cell gc; 331 u_int lines, i, n, width = c->tty.sx; 332 int flags, force = 0, changed = 0, fg, bg; 333 struct options_entry *o; 334 union options_value *ov; 335 struct format_tree *ft; 336 char *expanded; 337 338 log_debug("%s enter", __func__); 339 340 /* Shouldn't get here if not the active screen. */ 341 if (sl->active != &sl->screen) 342 fatalx("not the active screen"); 343 344 /* No status line? */ 345 lines = status_line_size(c); 346 if (c->tty.sy == 0 || lines == 0) 347 return (1); 348 349 /* Create format tree. */ 350 flags = FORMAT_STATUS; 351 if (c->flags & CLIENT_STATUSFORCE) 352 flags |= FORMAT_FORCE; 353 ft = format_create(c, NULL, FORMAT_NONE, flags); 354 format_defaults(ft, c, NULL, NULL, NULL); 355 356 /* Set up default colour. */ 357 style_apply(&gc, s->options, "status-style", ft); 358 fg = options_get_number(s->options, "status-fg"); 359 if (fg != 8) 360 gc.fg = fg; 361 bg = options_get_number(s->options, "status-bg"); 362 if (bg != 8) 363 gc.bg = bg; 364 if (!grid_cells_equal(&gc, &sl->style)) { 365 force = 1; 366 memcpy(&sl->style, &gc, sizeof sl->style); 367 } 368 369 /* Resize the target screen. */ 370 if (screen_size_x(&sl->screen) != width || 371 screen_size_y(&sl->screen) != lines) { 372 screen_resize(&sl->screen, width, lines, 0); 373 changed = force = 1; 374 } 375 screen_write_start(&ctx, &sl->screen); 376 377 /* Write the status lines. */ 378 o = options_get(s->options, "status-format"); 379 if (o == NULL) { 380 for (n = 0; n < width * lines; n++) 381 screen_write_putc(&ctx, &gc, ' '); 382 } else { 383 for (i = 0; i < lines; i++) { 384 screen_write_cursormove(&ctx, 0, i, 0); 385 386 ov = options_array_get(o, i); 387 if (ov == NULL) { 388 for (n = 0; n < width; n++) 389 screen_write_putc(&ctx, &gc, ' '); 390 continue; 391 } 392 sle = &sl->entries[i]; 393 394 expanded = format_expand_time(ft, ov->string); 395 if (!force && 396 sle->expanded != NULL && 397 strcmp(expanded, sle->expanded) == 0) { 398 free(expanded); 399 continue; 400 } 401 changed = 1; 402 403 for (n = 0; n < width; n++) 404 screen_write_putc(&ctx, &gc, ' '); 405 screen_write_cursormove(&ctx, 0, i, 0); 406 407 status_free_ranges(&sle->ranges); 408 format_draw(&ctx, &gc, width, expanded, &sle->ranges); 409 410 free(sle->expanded); 411 sle->expanded = expanded; 412 } 413 } 414 screen_write_stop(&ctx); 415 416 /* Free the format tree. */ 417 format_free(ft); 418 419 /* Return if the status line has changed. */ 420 log_debug("%s exit: force=%d, changed=%d", __func__, force, changed); 421 return (force || changed); 422 } 423 424 /* Set a status line message. */ 425 void 426 status_message_set(struct client *c, int delay, int ignore_styles, 427 int ignore_keys, const char *fmt, ...) 428 { 429 struct timeval tv; 430 va_list ap; 431 432 status_message_clear(c); 433 status_push_screen(c); 434 435 va_start(ap, fmt); 436 xvasprintf(&c->message_string, fmt, ap); 437 va_end(ap); 438 439 server_add_message("%s message: %s", c->name, c->message_string); 440 441 /* 442 * With delay -1, the display-time option is used; zero means wait for 443 * key press; more than zero is the actual delay time in milliseconds. 444 */ 445 if (delay == -1) 446 delay = options_get_number(c->session->options, "display-time"); 447 if (delay > 0) { 448 tv.tv_sec = delay / 1000; 449 tv.tv_usec = (delay % 1000) * 1000L; 450 451 if (event_initialized(&c->message_timer)) 452 evtimer_del(&c->message_timer); 453 evtimer_set(&c->message_timer, status_message_callback, c); 454 455 evtimer_add(&c->message_timer, &tv); 456 } 457 458 if (delay != 0) 459 c->message_ignore_keys = ignore_keys; 460 c->message_ignore_styles = ignore_styles; 461 462 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE); 463 c->flags |= CLIENT_REDRAWSTATUS; 464 } 465 466 /* Clear status line message. */ 467 void 468 status_message_clear(struct client *c) 469 { 470 if (c->message_string == NULL) 471 return; 472 473 free(c->message_string); 474 c->message_string = NULL; 475 476 if (c->prompt_string == NULL) 477 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE); 478 c->flags |= CLIENT_ALLREDRAWFLAGS; /* was frozen and may have changed */ 479 480 status_pop_screen(c); 481 } 482 483 /* Clear status line message after timer expires. */ 484 static void 485 status_message_callback(__unused int fd, __unused short event, void *data) 486 { 487 struct client *c = data; 488 489 status_message_clear(c); 490 } 491 492 /* Draw client message on status line of present else on last line. */ 493 int 494 status_message_redraw(struct client *c) 495 { 496 struct status_line *sl = &c->status; 497 struct screen_write_ctx ctx; 498 struct session *s = c->session; 499 struct screen old_screen; 500 size_t len; 501 u_int lines, offset; 502 struct grid_cell gc; 503 struct format_tree *ft; 504 505 if (c->tty.sx == 0 || c->tty.sy == 0) 506 return (0); 507 memcpy(&old_screen, sl->active, sizeof old_screen); 508 509 lines = status_line_size(c); 510 if (lines <= 1) 511 lines = 1; 512 screen_init(sl->active, c->tty.sx, lines, 0); 513 514 len = screen_write_strlen("%s", c->message_string); 515 if (len > c->tty.sx) 516 len = c->tty.sx; 517 518 ft = format_create_defaults(NULL, c, NULL, NULL, NULL); 519 style_apply(&gc, s->options, "message-style", ft); 520 format_free(ft); 521 522 screen_write_start(&ctx, sl->active); 523 screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines - 1); 524 screen_write_cursormove(&ctx, 0, lines - 1, 0); 525 for (offset = 0; offset < c->tty.sx; offset++) 526 screen_write_putc(&ctx, &gc, ' '); 527 screen_write_cursormove(&ctx, 0, lines - 1, 0); 528 if (c->message_ignore_styles) 529 screen_write_nputs(&ctx, len, &gc, "%s", c->message_string); 530 else 531 format_draw(&ctx, &gc, c->tty.sx, c->message_string, NULL); 532 screen_write_stop(&ctx); 533 534 if (grid_compare(sl->active->grid, old_screen.grid) == 0) { 535 screen_free(&old_screen); 536 return (0); 537 } 538 screen_free(&old_screen); 539 return (1); 540 } 541 542 /* Enable status line prompt. */ 543 void 544 status_prompt_set(struct client *c, struct cmd_find_state *fs, 545 const char *msg, const char *input, prompt_input_cb inputcb, 546 prompt_free_cb freecb, void *data, int flags) 547 { 548 struct format_tree *ft; 549 char *tmp; 550 551 if (fs != NULL) 552 ft = format_create_from_state(NULL, c, fs); 553 else 554 ft = format_create_defaults(NULL, c, NULL, NULL, NULL); 555 556 if (input == NULL) 557 input = ""; 558 if (flags & PROMPT_NOFORMAT) 559 tmp = xstrdup(input); 560 else 561 tmp = format_expand_time(ft, input); 562 563 status_message_clear(c); 564 status_prompt_clear(c); 565 status_push_screen(c); 566 567 c->prompt_string = format_expand_time(ft, msg); 568 569 if (flags & PROMPT_INCREMENTAL) { 570 c->prompt_last = xstrdup(tmp); 571 c->prompt_buffer = utf8_fromcstr(""); 572 } else { 573 c->prompt_last = NULL; 574 c->prompt_buffer = utf8_fromcstr(tmp); 575 } 576 c->prompt_index = utf8_strlen(c->prompt_buffer); 577 578 c->prompt_inputcb = inputcb; 579 c->prompt_freecb = freecb; 580 c->prompt_data = data; 581 582 c->prompt_hindex = 0; 583 584 c->prompt_flags = flags; 585 c->prompt_mode = PROMPT_ENTRY; 586 587 if (~flags & PROMPT_INCREMENTAL) 588 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE); 589 c->flags |= CLIENT_REDRAWSTATUS; 590 591 if (flags & PROMPT_INCREMENTAL) 592 c->prompt_inputcb(c, c->prompt_data, "=", 0); 593 594 free(tmp); 595 format_free(ft); 596 } 597 598 /* Remove status line prompt. */ 599 void 600 status_prompt_clear(struct client *c) 601 { 602 if (c->prompt_string == NULL) 603 return; 604 605 if (c->prompt_freecb != NULL && c->prompt_data != NULL) 606 c->prompt_freecb(c->prompt_data); 607 608 free(c->prompt_last); 609 c->prompt_last = NULL; 610 611 free(c->prompt_string); 612 c->prompt_string = NULL; 613 614 free(c->prompt_buffer); 615 c->prompt_buffer = NULL; 616 617 free(c->prompt_saved); 618 c->prompt_saved = NULL; 619 620 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE); 621 c->flags |= CLIENT_ALLREDRAWFLAGS; /* was frozen and may have changed */ 622 623 status_pop_screen(c); 624 } 625 626 /* Update status line prompt with a new prompt string. */ 627 void 628 status_prompt_update(struct client *c, const char *msg, const char *input) 629 { 630 struct format_tree *ft; 631 char *tmp; 632 633 ft = format_create(c, NULL, FORMAT_NONE, 0); 634 format_defaults(ft, c, NULL, NULL, NULL); 635 636 tmp = format_expand_time(ft, input); 637 638 free(c->prompt_string); 639 c->prompt_string = format_expand_time(ft, msg); 640 641 free(c->prompt_buffer); 642 c->prompt_buffer = utf8_fromcstr(tmp); 643 c->prompt_index = utf8_strlen(c->prompt_buffer); 644 645 c->prompt_hindex = 0; 646 647 c->flags |= CLIENT_REDRAWSTATUS; 648 649 free(tmp); 650 format_free(ft); 651 } 652 653 /* Draw client prompt on status line of present else on last line. */ 654 int 655 status_prompt_redraw(struct client *c) 656 { 657 struct status_line *sl = &c->status; 658 struct screen_write_ctx ctx; 659 struct session *s = c->session; 660 struct screen old_screen; 661 u_int i, lines, offset, left, start, width; 662 u_int pcursor, pwidth; 663 struct grid_cell gc, cursorgc; 664 struct format_tree *ft; 665 666 if (c->tty.sx == 0 || c->tty.sy == 0) 667 return (0); 668 memcpy(&old_screen, sl->active, sizeof old_screen); 669 670 lines = status_line_size(c); 671 if (lines <= 1) 672 lines = 1; 673 screen_init(sl->active, c->tty.sx, lines, 0); 674 675 ft = format_create_defaults(NULL, c, NULL, NULL, NULL); 676 if (c->prompt_mode == PROMPT_COMMAND) 677 style_apply(&gc, s->options, "message-command-style", ft); 678 else 679 style_apply(&gc, s->options, "message-style", ft); 680 format_free(ft); 681 682 memcpy(&cursorgc, &gc, sizeof cursorgc); 683 cursorgc.attr ^= GRID_ATTR_REVERSE; 684 685 start = screen_write_strlen("%s", c->prompt_string); 686 if (start > c->tty.sx) 687 start = c->tty.sx; 688 689 screen_write_start(&ctx, sl->active); 690 screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines - 1); 691 screen_write_cursormove(&ctx, 0, lines - 1, 0); 692 for (offset = 0; offset < c->tty.sx; offset++) 693 screen_write_putc(&ctx, &gc, ' '); 694 screen_write_cursormove(&ctx, 0, lines - 1, 0); 695 screen_write_nputs(&ctx, start, &gc, "%s", c->prompt_string); 696 screen_write_cursormove(&ctx, start, lines - 1, 0); 697 698 left = c->tty.sx - start; 699 if (left == 0) 700 goto finished; 701 702 pcursor = utf8_strwidth(c->prompt_buffer, c->prompt_index); 703 pwidth = utf8_strwidth(c->prompt_buffer, -1); 704 if (pcursor >= left) { 705 /* 706 * The cursor would be outside the screen so start drawing 707 * with it on the right. 708 */ 709 offset = (pcursor - left) + 1; 710 pwidth = left; 711 } else 712 offset = 0; 713 if (pwidth > left) 714 pwidth = left; 715 716 width = 0; 717 for (i = 0; c->prompt_buffer[i].size != 0; i++) { 718 if (width < offset) { 719 width += c->prompt_buffer[i].width; 720 continue; 721 } 722 if (width >= offset + pwidth) 723 break; 724 width += c->prompt_buffer[i].width; 725 if (width > offset + pwidth) 726 break; 727 728 if (i != c->prompt_index) { 729 utf8_copy(&gc.data, &c->prompt_buffer[i]); 730 screen_write_cell(&ctx, &gc); 731 } else { 732 utf8_copy(&cursorgc.data, &c->prompt_buffer[i]); 733 screen_write_cell(&ctx, &cursorgc); 734 } 735 } 736 if (sl->active->cx < screen_size_x(sl->active) && c->prompt_index >= i) 737 screen_write_putc(&ctx, &cursorgc, ' '); 738 739 finished: 740 screen_write_stop(&ctx); 741 742 if (grid_compare(sl->active->grid, old_screen.grid) == 0) { 743 screen_free(&old_screen); 744 return (0); 745 } 746 screen_free(&old_screen); 747 return (1); 748 } 749 750 /* Is this a separator? */ 751 static int 752 status_prompt_in_list(const char *ws, const struct utf8_data *ud) 753 { 754 if (ud->size != 1 || ud->width != 1) 755 return (0); 756 return (strchr(ws, *ud->data) != NULL); 757 } 758 759 /* Is this a space? */ 760 static int 761 status_prompt_space(const struct utf8_data *ud) 762 { 763 if (ud->size != 1 || ud->width != 1) 764 return (0); 765 return (*ud->data == ' '); 766 } 767 768 /* 769 * Translate key from emacs to vi. Return 0 to drop key, 1 to process the key 770 * as an emacs key; return 2 to append to the buffer. 771 */ 772 static int 773 status_prompt_translate_key(struct client *c, key_code key, key_code *new_key) 774 { 775 if (c->prompt_mode == PROMPT_ENTRY) { 776 switch (key) { 777 case '\003': /* C-c */ 778 case '\007': /* C-g */ 779 case '\010': /* C-h */ 780 case '\011': /* Tab */ 781 case '\025': /* C-u */ 782 case '\027': /* C-w */ 783 case '\n': 784 case '\r': 785 case KEYC_BSPACE: 786 case KEYC_DC: 787 case KEYC_DOWN: 788 case KEYC_END: 789 case KEYC_HOME: 790 case KEYC_LEFT: 791 case KEYC_RIGHT: 792 case KEYC_UP: 793 *new_key = key; 794 return (1); 795 case '\033': /* Escape */ 796 c->prompt_mode = PROMPT_COMMAND; 797 c->flags |= CLIENT_REDRAWSTATUS; 798 return (0); 799 } 800 *new_key = key; 801 return (2); 802 } 803 804 switch (key) { 805 case 'A': 806 case 'I': 807 case 'C': 808 case 's': 809 case 'a': 810 c->prompt_mode = PROMPT_ENTRY; 811 c->flags |= CLIENT_REDRAWSTATUS; 812 break; /* switch mode and... */ 813 case 'S': 814 c->prompt_mode = PROMPT_ENTRY; 815 c->flags |= CLIENT_REDRAWSTATUS; 816 *new_key = '\025'; /* C-u */ 817 return (1); 818 case 'i': 819 case '\033': /* Escape */ 820 c->prompt_mode = PROMPT_ENTRY; 821 c->flags |= CLIENT_REDRAWSTATUS; 822 return (0); 823 } 824 825 switch (key) { 826 case 'A': 827 case '$': 828 *new_key = KEYC_END; 829 return (1); 830 case 'I': 831 case '0': 832 case '^': 833 *new_key = KEYC_HOME; 834 return (1); 835 case 'C': 836 case 'D': 837 *new_key = '\013'; /* C-k */ 838 return (1); 839 case KEYC_BSPACE: 840 case 'X': 841 *new_key = KEYC_BSPACE; 842 return (1); 843 case 'b': 844 case 'B': 845 *new_key = 'b'|KEYC_META; 846 return (1); 847 case 'd': 848 *new_key = '\025'; 849 return (1); 850 case 'e': 851 case 'E': 852 case 'w': 853 case 'W': 854 *new_key = 'f'|KEYC_META; 855 return (1); 856 case 'p': 857 *new_key = '\031'; /* C-y */ 858 return (1); 859 case 'q': 860 *new_key = '\003'; /* C-c */ 861 return (1); 862 case 's': 863 case KEYC_DC: 864 case 'x': 865 *new_key = KEYC_DC; 866 return (1); 867 case KEYC_DOWN: 868 case 'j': 869 *new_key = KEYC_DOWN; 870 return (1); 871 case KEYC_LEFT: 872 case 'h': 873 *new_key = KEYC_LEFT; 874 return (1); 875 case 'a': 876 case KEYC_RIGHT: 877 case 'l': 878 *new_key = KEYC_RIGHT; 879 return (1); 880 case KEYC_UP: 881 case 'k': 882 *new_key = KEYC_UP; 883 return (1); 884 case '\010' /* C-h */: 885 case '\003' /* C-c */: 886 case '\n': 887 case '\r': 888 return (1); 889 } 890 return (0); 891 } 892 893 /* Paste into prompt. */ 894 static int 895 status_prompt_paste(struct client *c) 896 { 897 struct paste_buffer *pb; 898 const char *bufdata; 899 size_t size, n, bufsize; 900 u_int i; 901 struct utf8_data *ud, *udp; 902 enum utf8_state more; 903 904 size = utf8_strlen(c->prompt_buffer); 905 if (c->prompt_saved != NULL) { 906 ud = c->prompt_saved; 907 n = utf8_strlen(c->prompt_saved); 908 } else { 909 if ((pb = paste_get_top(NULL)) == NULL) 910 return (0); 911 bufdata = paste_buffer_data(pb, &bufsize); 912 ud = xreallocarray(NULL, bufsize + 1, sizeof *ud); 913 udp = ud; 914 for (i = 0; i != bufsize; /* nothing */) { 915 more = utf8_open(udp, bufdata[i]); 916 if (more == UTF8_MORE) { 917 while (++i != bufsize && more == UTF8_MORE) 918 more = utf8_append(udp, bufdata[i]); 919 if (more == UTF8_DONE) { 920 udp++; 921 continue; 922 } 923 i -= udp->have; 924 } 925 if (bufdata[i] <= 31 || bufdata[i] >= 127) 926 break; 927 utf8_set(udp, bufdata[i]); 928 udp++; 929 i++; 930 } 931 udp->size = 0; 932 n = udp - ud; 933 } 934 if (n == 0) 935 return (0); 936 937 c->prompt_buffer = xreallocarray(c->prompt_buffer, size + n + 1, 938 sizeof *c->prompt_buffer); 939 if (c->prompt_index == size) { 940 memcpy(c->prompt_buffer + c->prompt_index, ud, 941 n * sizeof *c->prompt_buffer); 942 c->prompt_index += n; 943 c->prompt_buffer[c->prompt_index].size = 0; 944 } else { 945 memmove(c->prompt_buffer + c->prompt_index + n, 946 c->prompt_buffer + c->prompt_index, 947 (size + 1 - c->prompt_index) * sizeof *c->prompt_buffer); 948 memcpy(c->prompt_buffer + c->prompt_index, ud, 949 n * sizeof *c->prompt_buffer); 950 c->prompt_index += n; 951 } 952 953 if (ud != c->prompt_saved) 954 free(ud); 955 return (1); 956 } 957 958 /* Finish completion. */ 959 static int 960 status_prompt_replace_complete(struct client *c, const char *s) 961 { 962 char word[64], *allocated = NULL; 963 size_t size, n, off, idx, used; 964 struct utf8_data *first, *last, *ud; 965 966 /* Work out where the cursor currently is. */ 967 idx = c->prompt_index; 968 if (idx != 0) 969 idx--; 970 size = utf8_strlen(c->prompt_buffer); 971 972 /* Find the word we are in. */ 973 first = &c->prompt_buffer[idx]; 974 while (first > c->prompt_buffer && !status_prompt_space(first)) 975 first--; 976 while (first->size != 0 && status_prompt_space(first)) 977 first++; 978 last = &c->prompt_buffer[idx]; 979 while (last->size != 0 && !status_prompt_space(last)) 980 last++; 981 while (last > c->prompt_buffer && status_prompt_space(last)) 982 last--; 983 if (last->size != 0) 984 last++; 985 if (last < first) 986 return (0); 987 if (s == NULL) { 988 used = 0; 989 for (ud = first; ud < last; ud++) { 990 if (used + ud->size >= sizeof word) 991 break; 992 memcpy(word + used, ud->data, ud->size); 993 used += ud->size; 994 } 995 if (ud != last) 996 return (0); 997 word[used] = '\0'; 998 } 999 1000 /* Try to complete it. */ 1001 if (s == NULL) { 1002 allocated = status_prompt_complete(c, word, 1003 first - c->prompt_buffer); 1004 if (allocated == NULL) 1005 return (0); 1006 s = allocated; 1007 } 1008 1009 /* Trim out word. */ 1010 n = size - (last - c->prompt_buffer) + 1; /* with \0 */ 1011 memmove(first, last, n * sizeof *c->prompt_buffer); 1012 size -= last - first; 1013 1014 /* Insert the new word. */ 1015 size += strlen(s); 1016 off = first - c->prompt_buffer; 1017 c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 1, 1018 sizeof *c->prompt_buffer); 1019 first = c->prompt_buffer + off; 1020 memmove(first + strlen(s), first, n * sizeof *c->prompt_buffer); 1021 for (idx = 0; idx < strlen(s); idx++) 1022 utf8_set(&first[idx], s[idx]); 1023 c->prompt_index = (first - c->prompt_buffer) + strlen(s); 1024 1025 free(allocated); 1026 return (1); 1027 } 1028 1029 /* Handle keys in prompt. */ 1030 int 1031 status_prompt_key(struct client *c, key_code key) 1032 { 1033 struct options *oo = c->session->options; 1034 char *s, *cp, prefix = '='; 1035 const char *histstr, *ws = NULL, *keystring; 1036 size_t size, idx; 1037 struct utf8_data tmp; 1038 int keys; 1039 1040 if (c->prompt_flags & PROMPT_KEY) { 1041 keystring = key_string_lookup_key(key, 0); 1042 c->prompt_inputcb(c, c->prompt_data, keystring, 1); 1043 status_prompt_clear(c); 1044 return (0); 1045 } 1046 size = utf8_strlen(c->prompt_buffer); 1047 1048 if (c->prompt_flags & PROMPT_NUMERIC) { 1049 if (key >= '0' && key <= '9') 1050 goto append_key; 1051 s = utf8_tocstr(c->prompt_buffer); 1052 c->prompt_inputcb(c, c->prompt_data, s, 1); 1053 status_prompt_clear(c); 1054 free(s); 1055 return (1); 1056 } 1057 key &= ~KEYC_MASK_FLAGS; 1058 1059 keys = options_get_number(c->session->options, "status-keys"); 1060 if (keys == MODEKEY_VI) { 1061 switch (status_prompt_translate_key(c, key, &key)) { 1062 case 1: 1063 goto process_key; 1064 case 2: 1065 goto append_key; 1066 default: 1067 return (0); 1068 } 1069 } 1070 1071 process_key: 1072 switch (key) { 1073 case KEYC_LEFT: 1074 case '\002': /* C-b */ 1075 if (c->prompt_index > 0) { 1076 c->prompt_index--; 1077 break; 1078 } 1079 break; 1080 case KEYC_RIGHT: 1081 case '\006': /* C-f */ 1082 if (c->prompt_index < size) { 1083 c->prompt_index++; 1084 break; 1085 } 1086 break; 1087 case KEYC_HOME: 1088 case '\001': /* C-a */ 1089 if (c->prompt_index != 0) { 1090 c->prompt_index = 0; 1091 break; 1092 } 1093 break; 1094 case KEYC_END: 1095 case '\005': /* C-e */ 1096 if (c->prompt_index != size) { 1097 c->prompt_index = size; 1098 break; 1099 } 1100 break; 1101 case '\011': /* Tab */ 1102 if (status_prompt_replace_complete(c, NULL)) 1103 goto changed; 1104 break; 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 free(c->prompt_saved); 1162 c->prompt_saved = xcalloc(sizeof *c->prompt_buffer, 1163 (c->prompt_index - idx) + 1); 1164 memcpy(c->prompt_saved, c->prompt_buffer + idx, 1165 (c->prompt_index - idx) * sizeof *c->prompt_buffer); 1166 1167 memmove(c->prompt_buffer + idx, 1168 c->prompt_buffer + c->prompt_index, 1169 (size + 1 - c->prompt_index) * 1170 sizeof *c->prompt_buffer); 1171 memset(c->prompt_buffer + size - (c->prompt_index - idx), 1172 '\0', (c->prompt_index - idx) * sizeof *c->prompt_buffer); 1173 c->prompt_index = idx; 1174 1175 goto changed; 1176 case 'f'|KEYC_META: 1177 case KEYC_RIGHT|KEYC_CTRL: 1178 ws = options_get_string(oo, "word-separators"); 1179 1180 /* Find a 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 /* Find the separator at the end of the word. */ 1188 while (c->prompt_index != size) { 1189 idx = ++c->prompt_index; 1190 if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) 1191 break; 1192 } 1193 1194 /* Back up to the end-of-word like vi. */ 1195 if (options_get_number(oo, "status-keys") == MODEKEY_VI && 1196 c->prompt_index != 0) 1197 c->prompt_index--; 1198 1199 goto changed; 1200 case 'b'|KEYC_META: 1201 case KEYC_LEFT|KEYC_CTRL: 1202 ws = options_get_string(oo, "word-separators"); 1203 1204 /* Find a non-separator. */ 1205 while (c->prompt_index != 0) { 1206 idx = --c->prompt_index; 1207 if (!status_prompt_in_list(ws, &c->prompt_buffer[idx])) 1208 break; 1209 } 1210 1211 /* Find the separator at the beginning of the word. */ 1212 while (c->prompt_index != 0) { 1213 idx = --c->prompt_index; 1214 if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) { 1215 /* Go back to the word. */ 1216 c->prompt_index++; 1217 break; 1218 } 1219 } 1220 goto changed; 1221 case KEYC_UP: 1222 case '\020': /* C-p */ 1223 histstr = status_prompt_up_history(&c->prompt_hindex); 1224 if (histstr == NULL) 1225 break; 1226 free(c->prompt_buffer); 1227 c->prompt_buffer = utf8_fromcstr(histstr); 1228 c->prompt_index = utf8_strlen(c->prompt_buffer); 1229 goto changed; 1230 case KEYC_DOWN: 1231 case '\016': /* C-n */ 1232 histstr = status_prompt_down_history(&c->prompt_hindex); 1233 if (histstr == NULL) 1234 break; 1235 free(c->prompt_buffer); 1236 c->prompt_buffer = utf8_fromcstr(histstr); 1237 c->prompt_index = utf8_strlen(c->prompt_buffer); 1238 goto changed; 1239 case '\031': /* C-y */ 1240 if (status_prompt_paste(c)) 1241 goto changed; 1242 break; 1243 case '\024': /* C-t */ 1244 idx = c->prompt_index; 1245 if (idx < size) 1246 idx++; 1247 if (idx >= 2) { 1248 utf8_copy(&tmp, &c->prompt_buffer[idx - 2]); 1249 utf8_copy(&c->prompt_buffer[idx - 2], 1250 &c->prompt_buffer[idx - 1]); 1251 utf8_copy(&c->prompt_buffer[idx - 1], &tmp); 1252 c->prompt_index = idx; 1253 goto changed; 1254 } 1255 break; 1256 case '\r': 1257 case '\n': 1258 s = utf8_tocstr(c->prompt_buffer); 1259 if (*s != '\0') 1260 status_prompt_add_history(s); 1261 if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0) 1262 status_prompt_clear(c); 1263 free(s); 1264 break; 1265 case '\033': /* Escape */ 1266 case '\003': /* C-c */ 1267 case '\007': /* C-g */ 1268 if (c->prompt_inputcb(c, c->prompt_data, NULL, 1) == 0) 1269 status_prompt_clear(c); 1270 break; 1271 case '\022': /* C-r */ 1272 if (~c->prompt_flags & PROMPT_INCREMENTAL) 1273 break; 1274 if (c->prompt_buffer[0].size == 0) { 1275 prefix = '='; 1276 free (c->prompt_buffer); 1277 c->prompt_buffer = utf8_fromcstr(c->prompt_last); 1278 c->prompt_index = utf8_strlen(c->prompt_buffer); 1279 } else 1280 prefix = '-'; 1281 goto changed; 1282 case '\023': /* C-s */ 1283 if (~c->prompt_flags & PROMPT_INCREMENTAL) 1284 break; 1285 if (c->prompt_buffer[0].size == 0) { 1286 prefix = '='; 1287 free (c->prompt_buffer); 1288 c->prompt_buffer = utf8_fromcstr(c->prompt_last); 1289 c->prompt_index = utf8_strlen(c->prompt_buffer); 1290 } else 1291 prefix = '+'; 1292 goto changed; 1293 default: 1294 goto append_key; 1295 } 1296 1297 c->flags |= CLIENT_REDRAWSTATUS; 1298 return (0); 1299 1300 append_key: 1301 if (key <= 0x1f || key >= KEYC_BASE) 1302 return (0); 1303 if (key <= 0x7f) 1304 utf8_set(&tmp, key); 1305 else 1306 utf8_to_data(key, &tmp); 1307 1308 c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 2, 1309 sizeof *c->prompt_buffer); 1310 1311 if (c->prompt_index == size) { 1312 utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp); 1313 c->prompt_index++; 1314 c->prompt_buffer[c->prompt_index].size = 0; 1315 } else { 1316 memmove(c->prompt_buffer + c->prompt_index + 1, 1317 c->prompt_buffer + c->prompt_index, 1318 (size + 1 - c->prompt_index) * 1319 sizeof *c->prompt_buffer); 1320 utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp); 1321 c->prompt_index++; 1322 } 1323 1324 if (c->prompt_flags & PROMPT_SINGLE) { 1325 if (utf8_strlen(c->prompt_buffer) != 1) 1326 status_prompt_clear(c); 1327 else { 1328 s = utf8_tocstr(c->prompt_buffer); 1329 if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0) 1330 status_prompt_clear(c); 1331 free(s); 1332 } 1333 } 1334 1335 changed: 1336 c->flags |= CLIENT_REDRAWSTATUS; 1337 if (c->prompt_flags & PROMPT_INCREMENTAL) { 1338 s = utf8_tocstr(c->prompt_buffer); 1339 xasprintf(&cp, "%c%s", prefix, s); 1340 c->prompt_inputcb(c, c->prompt_data, cp, 0); 1341 free(cp); 1342 free(s); 1343 } 1344 return (0); 1345 } 1346 1347 /* Get previous line from the history. */ 1348 static const char * 1349 status_prompt_up_history(u_int *idx) 1350 { 1351 /* 1352 * History runs from 0 to size - 1. Index is from 0 to size. Zero is 1353 * empty. 1354 */ 1355 1356 if (status_prompt_hsize == 0 || *idx == status_prompt_hsize) 1357 return (NULL); 1358 (*idx)++; 1359 return (status_prompt_hlist[status_prompt_hsize - *idx]); 1360 } 1361 1362 /* Get next line from the history. */ 1363 static const char * 1364 status_prompt_down_history(u_int *idx) 1365 { 1366 if (status_prompt_hsize == 0 || *idx == 0) 1367 return (""); 1368 (*idx)--; 1369 if (*idx == 0) 1370 return (""); 1371 return (status_prompt_hlist[status_prompt_hsize - *idx]); 1372 } 1373 1374 /* Add line to the history. */ 1375 static void 1376 status_prompt_add_history(const char *line) 1377 { 1378 size_t size; 1379 1380 if (status_prompt_hsize > 0 && 1381 strcmp(status_prompt_hlist[status_prompt_hsize - 1], line) == 0) 1382 return; 1383 1384 if (status_prompt_hsize == PROMPT_HISTORY) { 1385 free(status_prompt_hlist[0]); 1386 1387 size = (PROMPT_HISTORY - 1) * sizeof *status_prompt_hlist; 1388 memmove(&status_prompt_hlist[0], &status_prompt_hlist[1], size); 1389 1390 status_prompt_hlist[status_prompt_hsize - 1] = xstrdup(line); 1391 return; 1392 } 1393 1394 status_prompt_hlist = xreallocarray(status_prompt_hlist, 1395 status_prompt_hsize + 1, sizeof *status_prompt_hlist); 1396 status_prompt_hlist[status_prompt_hsize++] = xstrdup(line); 1397 } 1398 1399 /* Build completion list. */ 1400 static char ** 1401 status_prompt_complete_list(u_int *size, const char *s, int at_start) 1402 { 1403 char **list = NULL; 1404 const char **layout, *value, *cp; 1405 const struct cmd_entry **cmdent; 1406 const struct options_table_entry *oe; 1407 size_t slen = strlen(s), valuelen; 1408 struct options_entry *o; 1409 struct options_array_item *a; 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, slen) == 0) { 1418 list = xreallocarray(list, (*size) + 1, sizeof *list); 1419 list[(*size)++] = xstrdup((*cmdent)->name); 1420 } 1421 if ((*cmdent)->alias != NULL && 1422 strncmp((*cmdent)->alias, s, slen) == 0) { 1423 list = xreallocarray(list, (*size) + 1, sizeof *list); 1424 list[(*size)++] = xstrdup((*cmdent)->alias); 1425 } 1426 } 1427 o = options_get_only(global_options, "command-alias"); 1428 if (o != NULL) { 1429 a = options_array_first(o); 1430 while (a != NULL) { 1431 value = options_array_item_value(a)->string; 1432 if ((cp = strchr(value, '=')) == NULL) 1433 goto next; 1434 valuelen = cp - value; 1435 if (slen > valuelen || strncmp(value, s, slen) != 0) 1436 goto next; 1437 1438 list = xreallocarray(list, (*size) + 1, sizeof *list); 1439 list[(*size)++] = xstrndup(value, valuelen); 1440 1441 next: 1442 a = options_array_next(a); 1443 } 1444 } 1445 if (at_start) 1446 return (list); 1447 1448 for (oe = options_table; oe->name != NULL; oe++) { 1449 if (strncmp(oe->name, s, slen) == 0) { 1450 list = xreallocarray(list, (*size) + 1, sizeof *list); 1451 list[(*size)++] = xstrdup(oe->name); 1452 } 1453 } 1454 for (layout = layouts; *layout != NULL; layout++) { 1455 if (strncmp(*layout, s, slen) == 0) { 1456 list = xreallocarray(list, (*size) + 1, sizeof *list); 1457 list[(*size)++] = xstrdup(*layout); 1458 } 1459 } 1460 return (list); 1461 } 1462 1463 /* Find longest prefix. */ 1464 static char * 1465 status_prompt_complete_prefix(char **list, u_int size) 1466 { 1467 char *out; 1468 u_int i; 1469 size_t j; 1470 1471 if (list == NULL || size == 0) 1472 return (NULL); 1473 out = xstrdup(list[0]); 1474 for (i = 1; i < size; i++) { 1475 j = strlen(list[i]); 1476 if (j > strlen(out)) 1477 j = strlen(out); 1478 for (; j > 0; j--) { 1479 if (out[j - 1] != list[i][j - 1]) 1480 out[j - 1] = '\0'; 1481 } 1482 } 1483 return (out); 1484 } 1485 1486 /* Complete word menu callback. */ 1487 static void 1488 status_prompt_menu_callback(__unused struct menu *menu, u_int idx, key_code key, 1489 void *data) 1490 { 1491 struct status_prompt_menu *spm = data; 1492 struct client *c = spm->c; 1493 u_int i; 1494 char *s; 1495 1496 if (key != KEYC_NONE) { 1497 idx += spm->start; 1498 if (spm->flag == '\0') 1499 s = xstrdup(spm->list[idx]); 1500 else 1501 xasprintf(&s, "-%c%s", spm->flag, spm->list[idx]); 1502 if (c->prompt_flags & PROMPT_WINDOW) { 1503 free(c->prompt_buffer); 1504 c->prompt_buffer = utf8_fromcstr(s); 1505 c->prompt_index = utf8_strlen(c->prompt_buffer); 1506 c->flags |= CLIENT_REDRAWSTATUS; 1507 } else if (status_prompt_replace_complete(c, s)) 1508 c->flags |= CLIENT_REDRAWSTATUS; 1509 free(s); 1510 } 1511 1512 for (i = 0; i < spm->size; i++) 1513 free(spm->list[i]); 1514 free(spm->list); 1515 } 1516 1517 /* Show complete word menu. */ 1518 static int 1519 status_prompt_complete_list_menu(struct client *c, char **list, u_int size, 1520 u_int offset, char flag) 1521 { 1522 struct menu *menu; 1523 struct menu_item item; 1524 struct status_prompt_menu *spm; 1525 u_int lines = status_line_size(c), height, i; 1526 u_int py; 1527 1528 if (size <= 1) 1529 return (0); 1530 if (c->tty.sy - lines < 3) 1531 return (0); 1532 1533 spm = xmalloc(sizeof *spm); 1534 spm->c = c; 1535 spm->size = size; 1536 spm->list = list; 1537 spm->flag = flag; 1538 1539 height = c->tty.sy - lines - 2; 1540 if (height > 10) 1541 height = 10; 1542 if (height > size) 1543 height = size; 1544 spm->start = size - height; 1545 1546 menu = menu_create(""); 1547 for (i = spm->start; i < size; i++) { 1548 item.name = list[i]; 1549 item.key = '0' + (i - spm->start); 1550 item.command = NULL; 1551 menu_add_item(menu, &item, NULL, NULL, NULL); 1552 } 1553 1554 if (options_get_number(c->session->options, "status-position") == 0) 1555 py = lines; 1556 else 1557 py = c->tty.sy - 3 - height; 1558 offset += utf8_cstrwidth(c->prompt_string); 1559 if (offset > 2) 1560 offset -= 2; 1561 else 1562 offset = 0; 1563 1564 if (menu_display(menu, MENU_NOMOUSE|MENU_TAB, NULL, offset, 1565 py, c, NULL, status_prompt_menu_callback, spm) != 0) { 1566 menu_free(menu); 1567 free(spm); 1568 return (0); 1569 } 1570 return (1); 1571 } 1572 1573 /* Show complete word menu. */ 1574 static char * 1575 status_prompt_complete_window_menu(struct client *c, struct session *s, 1576 const char *word, u_int offset, char flag) 1577 { 1578 struct menu *menu; 1579 struct menu_item item; 1580 struct status_prompt_menu *spm; 1581 struct winlink *wl; 1582 char **list = NULL, *tmp; 1583 u_int lines = status_line_size(c), height; 1584 u_int py, size = 0; 1585 1586 if (c->tty.sy - lines < 3) 1587 return (NULL); 1588 1589 spm = xmalloc(sizeof *spm); 1590 spm->c = c; 1591 spm->flag = flag; 1592 1593 height = c->tty.sy - lines - 2; 1594 if (height > 10) 1595 height = 10; 1596 spm->start = 0; 1597 1598 menu = menu_create(""); 1599 RB_FOREACH(wl, winlinks, &s->windows) { 1600 if (word != NULL && *word != '\0') { 1601 xasprintf(&tmp, "%d", wl->idx); 1602 if (strncmp(tmp, word, strlen(word)) != 0) { 1603 free(tmp); 1604 continue; 1605 } 1606 free(tmp); 1607 } 1608 1609 list = xreallocarray(list, size + 1, sizeof *list); 1610 if (c->prompt_flags & PROMPT_WINDOW) { 1611 xasprintf(&tmp, "%d (%s)", wl->idx, wl->window->name); 1612 xasprintf(&list[size++], "%d", wl->idx); 1613 } else { 1614 xasprintf(&tmp, "%s:%d (%s)", s->name, wl->idx, 1615 wl->window->name); 1616 xasprintf(&list[size++], "%s:%d", s->name, wl->idx); 1617 } 1618 item.name = tmp; 1619 item.key = '0' + size - 1; 1620 item.command = NULL; 1621 menu_add_item(menu, &item, NULL, NULL, NULL); 1622 free(tmp); 1623 1624 if (size == height) 1625 break; 1626 } 1627 if (size == 0) { 1628 menu_free(menu); 1629 return (NULL); 1630 } 1631 if (size == 1) { 1632 menu_free(menu); 1633 if (flag != '\0') { 1634 xasprintf(&tmp, "-%c%s", flag, list[0]); 1635 free(list[0]); 1636 } else 1637 tmp = list[0]; 1638 free(list); 1639 return (tmp); 1640 } 1641 if (height > size) 1642 height = size; 1643 1644 spm->size = size; 1645 spm->list = list; 1646 1647 if (options_get_number(c->session->options, "status-position") == 0) 1648 py = lines; 1649 else 1650 py = c->tty.sy - 3 - height; 1651 offset += utf8_cstrwidth(c->prompt_string); 1652 if (offset > 2) 1653 offset -= 2; 1654 else 1655 offset = 0; 1656 1657 if (menu_display(menu, MENU_NOMOUSE|MENU_TAB, NULL, offset, 1658 py, c, NULL, status_prompt_menu_callback, spm) != 0) { 1659 menu_free(menu); 1660 free(spm); 1661 return (NULL); 1662 } 1663 return (NULL); 1664 } 1665 1666 /* Sort complete list. */ 1667 static int 1668 status_prompt_complete_sort(const void *a, const void *b) 1669 { 1670 const char **aa = (const char **)a, **bb = (const char **)b; 1671 1672 return (strcmp(*aa, *bb)); 1673 } 1674 1675 /* Complete a session. */ 1676 static char * 1677 status_prompt_complete_session(char ***list, u_int *size, const char *s, 1678 char flag) 1679 { 1680 struct session *loop; 1681 char *out, *tmp, n[11]; 1682 1683 RB_FOREACH(loop, sessions, &sessions) { 1684 if (*s == '\0' || strncmp(loop->name, s, strlen(s)) == 0) { 1685 *list = xreallocarray(*list, (*size) + 2, 1686 sizeof **list); 1687 xasprintf(&(*list)[(*size)++], "%s:", loop->name); 1688 } else if (*s == '$') { 1689 xsnprintf(n, sizeof n, "%u", loop->id); 1690 if (s[1] == '\0' || 1691 strncmp(n, s + 1, strlen(s) - 1) == 0) { 1692 *list = xreallocarray(*list, (*size) + 2, 1693 sizeof **list); 1694 xasprintf(&(*list)[(*size)++], "$%s:", n); 1695 } 1696 } 1697 } 1698 out = status_prompt_complete_prefix(*list, *size); 1699 if (out != NULL && flag != '\0') { 1700 xasprintf(&tmp, "-%c%s", flag, out); 1701 free(out); 1702 out = tmp; 1703 } 1704 return (out); 1705 } 1706 1707 /* Complete word. */ 1708 static char * 1709 status_prompt_complete(struct client *c, const char *word, u_int offset) 1710 { 1711 struct session *session; 1712 const char *s, *colon; 1713 char **list = NULL, *copy = NULL, *out = NULL; 1714 char flag = '\0'; 1715 u_int size = 0, i; 1716 1717 if (*word == '\0' && 1718 ((c->prompt_flags & (PROMPT_TARGET|PROMPT_WINDOW)) == 0)) 1719 return (NULL); 1720 1721 if (((c->prompt_flags & (PROMPT_TARGET|PROMPT_WINDOW)) == 0) && 1722 strncmp(word, "-t", 2) != 0 && 1723 strncmp(word, "-s", 2) != 0) { 1724 list = status_prompt_complete_list(&size, word, offset == 0); 1725 if (size == 0) 1726 out = NULL; 1727 else if (size == 1) 1728 xasprintf(&out, "%s ", list[0]); 1729 else 1730 out = status_prompt_complete_prefix(list, size); 1731 goto found; 1732 } 1733 1734 if (c->prompt_flags & (PROMPT_TARGET|PROMPT_WINDOW)) { 1735 s = word; 1736 flag = '\0'; 1737 } else { 1738 s = word + 2; 1739 flag = word[1]; 1740 offset += 2; 1741 } 1742 1743 /* If this is a window completion, open the window menu. */ 1744 if (c->prompt_flags & PROMPT_WINDOW) { 1745 out = status_prompt_complete_window_menu(c, c->session, s, 1746 offset, '\0'); 1747 goto found; 1748 } 1749 colon = strchr(s, ':'); 1750 1751 /* If there is no colon, complete as a session. */ 1752 if (colon == NULL) { 1753 out = status_prompt_complete_session(&list, &size, s, flag); 1754 goto found; 1755 } 1756 1757 /* If there is a colon but no period, find session and show a menu. */ 1758 if (strchr(colon + 1, '.') == NULL) { 1759 if (*s == ':') 1760 session = c->session; 1761 else { 1762 copy = xstrdup(s); 1763 *strchr(copy, ':') = '\0'; 1764 session = session_find(copy); 1765 free(copy); 1766 if (session == NULL) 1767 goto found; 1768 } 1769 out = status_prompt_complete_window_menu(c, session, colon + 1, 1770 offset, flag); 1771 if (out == NULL) 1772 return (NULL); 1773 } 1774 1775 found: 1776 if (size != 0) { 1777 qsort(list, size, sizeof *list, status_prompt_complete_sort); 1778 for (i = 0; i < size; i++) 1779 log_debug("complete %u: %s", i, list[i]); 1780 } 1781 1782 if (out != NULL && strcmp(word, out) == 0) { 1783 free(out); 1784 out = NULL; 1785 } 1786 if (out != NULL || 1787 !status_prompt_complete_list_menu(c, list, size, offset, flag)) { 1788 for (i = 0; i < size; i++) 1789 free(list[i]); 1790 free(list); 1791 } 1792 return (out); 1793 } 1794