1 /* $OpenBSD: status.c,v 1.220 2021/02/22 06:53:04 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 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 c->message_ignore_styles = ignore_styles; 437 xvasprintf(&c->message_string, fmt, ap); 438 va_end(ap); 439 440 server_add_message("%s message: %s", c->name, c->message_string); 441 442 /* 443 * With delay -1, the display-time option is used; zero means wait for 444 * key press; more than zero is the actual delay time in milliseconds. 445 */ 446 if (delay == -1) 447 delay = options_get_number(c->session->options, "display-time"); 448 if (delay > 0) { 449 tv.tv_sec = delay / 1000; 450 tv.tv_usec = (delay % 1000) * 1000L; 451 452 if (event_initialized(&c->message_timer)) 453 evtimer_del(&c->message_timer); 454 evtimer_set(&c->message_timer, status_message_callback, c); 455 456 evtimer_add(&c->message_timer, &tv); 457 } 458 459 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE); 460 c->flags |= CLIENT_REDRAWSTATUS; 461 } 462 463 /* Clear status line message. */ 464 void 465 status_message_clear(struct client *c) 466 { 467 if (c->message_string == NULL) 468 return; 469 470 free(c->message_string); 471 c->message_string = NULL; 472 473 if (c->prompt_string == NULL) 474 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE); 475 c->flags |= CLIENT_ALLREDRAWFLAGS; /* was frozen and may have changed */ 476 477 status_pop_screen(c); 478 } 479 480 /* Clear status line message after timer expires. */ 481 static void 482 status_message_callback(__unused int fd, __unused short event, void *data) 483 { 484 struct client *c = data; 485 486 status_message_clear(c); 487 } 488 489 /* Draw client message on status line of present else on last line. */ 490 int 491 status_message_redraw(struct client *c) 492 { 493 struct status_line *sl = &c->status; 494 struct screen_write_ctx ctx; 495 struct session *s = c->session; 496 struct screen old_screen; 497 size_t len; 498 u_int lines, offset; 499 struct grid_cell gc; 500 struct format_tree *ft; 501 502 if (c->tty.sx == 0 || c->tty.sy == 0) 503 return (0); 504 memcpy(&old_screen, sl->active, sizeof old_screen); 505 506 lines = status_line_size(c); 507 if (lines <= 1) 508 lines = 1; 509 screen_init(sl->active, c->tty.sx, lines, 0); 510 511 len = screen_write_strlen("%s", c->message_string); 512 if (len > c->tty.sx) 513 len = c->tty.sx; 514 515 ft = format_create_defaults(NULL, c, NULL, NULL, NULL); 516 style_apply(&gc, s->options, "message-style", ft); 517 format_free(ft); 518 519 screen_write_start(&ctx, sl->active); 520 screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines - 1); 521 screen_write_cursormove(&ctx, 0, lines - 1, 0); 522 for (offset = 0; offset < c->tty.sx; offset++) 523 screen_write_putc(&ctx, &gc, ' '); 524 screen_write_cursormove(&ctx, 0, lines - 1, 0); 525 if (c->message_ignore_styles) 526 screen_write_nputs(&ctx, len, &gc, "%s", c->message_string); 527 else 528 format_draw(&ctx, &gc, c->tty.sx, c->message_string, NULL); 529 screen_write_stop(&ctx); 530 531 if (grid_compare(sl->active->grid, old_screen.grid) == 0) { 532 screen_free(&old_screen); 533 return (0); 534 } 535 screen_free(&old_screen); 536 return (1); 537 } 538 539 /* Enable status line prompt. */ 540 void 541 status_prompt_set(struct client *c, struct cmd_find_state *fs, 542 const char *msg, const char *input, prompt_input_cb inputcb, 543 prompt_free_cb freecb, void *data, int flags) 544 { 545 struct format_tree *ft; 546 char *tmp; 547 548 if (fs != NULL) 549 ft = format_create_from_state(NULL, c, fs); 550 else 551 ft = format_create_defaults(NULL, c, NULL, NULL, NULL); 552 553 if (input == NULL) 554 input = ""; 555 if (flags & PROMPT_NOFORMAT) 556 tmp = xstrdup(input); 557 else 558 tmp = format_expand_time(ft, input); 559 560 status_message_clear(c); 561 status_prompt_clear(c); 562 status_push_screen(c); 563 564 c->prompt_string = format_expand_time(ft, msg); 565 566 if (flags & PROMPT_INCREMENTAL) { 567 c->prompt_last = xstrdup(tmp); 568 c->prompt_buffer = utf8_fromcstr(""); 569 } else { 570 c->prompt_last = NULL; 571 c->prompt_buffer = utf8_fromcstr(tmp); 572 } 573 c->prompt_index = utf8_strlen(c->prompt_buffer); 574 575 c->prompt_inputcb = inputcb; 576 c->prompt_freecb = freecb; 577 c->prompt_data = data; 578 579 c->prompt_hindex = 0; 580 581 c->prompt_flags = flags; 582 c->prompt_mode = PROMPT_ENTRY; 583 584 if (~flags & PROMPT_INCREMENTAL) 585 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE); 586 c->flags |= CLIENT_REDRAWSTATUS; 587 588 if (flags & PROMPT_INCREMENTAL) 589 c->prompt_inputcb(c, c->prompt_data, "=", 0); 590 591 free(tmp); 592 format_free(ft); 593 } 594 595 /* Remove status line prompt. */ 596 void 597 status_prompt_clear(struct client *c) 598 { 599 if (c->prompt_string == NULL) 600 return; 601 602 if (c->prompt_freecb != NULL && c->prompt_data != NULL) 603 c->prompt_freecb(c->prompt_data); 604 605 free(c->prompt_last); 606 c->prompt_last = NULL; 607 608 free(c->prompt_string); 609 c->prompt_string = NULL; 610 611 free(c->prompt_buffer); 612 c->prompt_buffer = NULL; 613 614 free(c->prompt_saved); 615 c->prompt_saved = NULL; 616 617 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE); 618 c->flags |= CLIENT_ALLREDRAWFLAGS; /* was frozen and may have changed */ 619 620 status_pop_screen(c); 621 } 622 623 /* Update status line prompt with a new prompt string. */ 624 void 625 status_prompt_update(struct client *c, const char *msg, const char *input) 626 { 627 struct format_tree *ft; 628 char *tmp; 629 630 ft = format_create(c, NULL, FORMAT_NONE, 0); 631 format_defaults(ft, c, NULL, NULL, NULL); 632 633 tmp = format_expand_time(ft, input); 634 635 free(c->prompt_string); 636 c->prompt_string = format_expand_time(ft, msg); 637 638 free(c->prompt_buffer); 639 c->prompt_buffer = utf8_fromcstr(tmp); 640 c->prompt_index = utf8_strlen(c->prompt_buffer); 641 642 c->prompt_hindex = 0; 643 644 c->flags |= CLIENT_REDRAWSTATUS; 645 646 free(tmp); 647 format_free(ft); 648 } 649 650 /* Draw client prompt on status line of present else on last line. */ 651 int 652 status_prompt_redraw(struct client *c) 653 { 654 struct status_line *sl = &c->status; 655 struct screen_write_ctx ctx; 656 struct session *s = c->session; 657 struct screen old_screen; 658 u_int i, lines, offset, left, start, width; 659 u_int pcursor, pwidth; 660 struct grid_cell gc, cursorgc; 661 struct format_tree *ft; 662 663 if (c->tty.sx == 0 || c->tty.sy == 0) 664 return (0); 665 memcpy(&old_screen, sl->active, sizeof old_screen); 666 667 lines = status_line_size(c); 668 if (lines <= 1) 669 lines = 1; 670 screen_init(sl->active, c->tty.sx, lines, 0); 671 672 ft = format_create_defaults(NULL, c, NULL, NULL, NULL); 673 if (c->prompt_mode == PROMPT_COMMAND) 674 style_apply(&gc, s->options, "message-command-style", ft); 675 else 676 style_apply(&gc, s->options, "message-style", ft); 677 format_free(ft); 678 679 memcpy(&cursorgc, &gc, sizeof cursorgc); 680 cursorgc.attr ^= GRID_ATTR_REVERSE; 681 682 start = screen_write_strlen("%s", c->prompt_string); 683 if (start > c->tty.sx) 684 start = c->tty.sx; 685 686 screen_write_start(&ctx, sl->active); 687 screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines - 1); 688 screen_write_cursormove(&ctx, 0, lines - 1, 0); 689 for (offset = 0; offset < c->tty.sx; offset++) 690 screen_write_putc(&ctx, &gc, ' '); 691 screen_write_cursormove(&ctx, 0, lines - 1, 0); 692 screen_write_nputs(&ctx, start, &gc, "%s", c->prompt_string); 693 screen_write_cursormove(&ctx, start, lines - 1, 0); 694 695 left = c->tty.sx - start; 696 if (left == 0) 697 goto finished; 698 699 pcursor = utf8_strwidth(c->prompt_buffer, c->prompt_index); 700 pwidth = utf8_strwidth(c->prompt_buffer, -1); 701 if (pcursor >= left) { 702 /* 703 * The cursor would be outside the screen so start drawing 704 * with it on the right. 705 */ 706 offset = (pcursor - left) + 1; 707 pwidth = left; 708 } else 709 offset = 0; 710 if (pwidth > left) 711 pwidth = left; 712 713 width = 0; 714 for (i = 0; c->prompt_buffer[i].size != 0; i++) { 715 if (width < offset) { 716 width += c->prompt_buffer[i].width; 717 continue; 718 } 719 if (width >= offset + pwidth) 720 break; 721 width += c->prompt_buffer[i].width; 722 if (width > offset + pwidth) 723 break; 724 725 if (i != c->prompt_index) { 726 utf8_copy(&gc.data, &c->prompt_buffer[i]); 727 screen_write_cell(&ctx, &gc); 728 } else { 729 utf8_copy(&cursorgc.data, &c->prompt_buffer[i]); 730 screen_write_cell(&ctx, &cursorgc); 731 } 732 } 733 if (sl->active->cx < screen_size_x(sl->active) && c->prompt_index >= i) 734 screen_write_putc(&ctx, &cursorgc, ' '); 735 736 finished: 737 screen_write_stop(&ctx); 738 739 if (grid_compare(sl->active->grid, old_screen.grid) == 0) { 740 screen_free(&old_screen); 741 return (0); 742 } 743 screen_free(&old_screen); 744 return (1); 745 } 746 747 /* Is this a separator? */ 748 static int 749 status_prompt_in_list(const char *ws, const struct utf8_data *ud) 750 { 751 if (ud->size != 1 || ud->width != 1) 752 return (0); 753 return (strchr(ws, *ud->data) != NULL); 754 } 755 756 /* Is this a space? */ 757 static int 758 status_prompt_space(const struct utf8_data *ud) 759 { 760 if (ud->size != 1 || ud->width != 1) 761 return (0); 762 return (*ud->data == ' '); 763 } 764 765 /* 766 * Translate key from emacs to vi. Return 0 to drop key, 1 to process the key 767 * as an emacs key; return 2 to append to the buffer. 768 */ 769 static int 770 status_prompt_translate_key(struct client *c, key_code key, key_code *new_key) 771 { 772 if (c->prompt_mode == PROMPT_ENTRY) { 773 switch (key) { 774 case '\003': /* C-c */ 775 case '\007': /* C-g */ 776 case '\010': /* C-h */ 777 case '\011': /* Tab */ 778 case '\025': /* C-u */ 779 case '\027': /* C-w */ 780 case '\n': 781 case '\r': 782 case KEYC_BSPACE: 783 case KEYC_DC: 784 case KEYC_DOWN: 785 case KEYC_END: 786 case KEYC_HOME: 787 case KEYC_LEFT: 788 case KEYC_RIGHT: 789 case KEYC_UP: 790 *new_key = key; 791 return (1); 792 case '\033': /* Escape */ 793 c->prompt_mode = PROMPT_COMMAND; 794 c->flags |= CLIENT_REDRAWSTATUS; 795 return (0); 796 } 797 *new_key = key; 798 return (2); 799 } 800 801 switch (key) { 802 case 'A': 803 case 'I': 804 case 'C': 805 case 's': 806 case 'a': 807 c->prompt_mode = PROMPT_ENTRY; 808 c->flags |= CLIENT_REDRAWSTATUS; 809 break; /* switch mode and... */ 810 case 'S': 811 c->prompt_mode = PROMPT_ENTRY; 812 c->flags |= CLIENT_REDRAWSTATUS; 813 *new_key = '\025'; /* C-u */ 814 return (1); 815 case 'i': 816 case '\033': /* Escape */ 817 c->prompt_mode = PROMPT_ENTRY; 818 c->flags |= CLIENT_REDRAWSTATUS; 819 return (0); 820 } 821 822 switch (key) { 823 case 'A': 824 case '$': 825 *new_key = KEYC_END; 826 return (1); 827 case 'I': 828 case '0': 829 case '^': 830 *new_key = KEYC_HOME; 831 return (1); 832 case 'C': 833 case 'D': 834 *new_key = '\013'; /* C-k */ 835 return (1); 836 case KEYC_BSPACE: 837 case 'X': 838 *new_key = KEYC_BSPACE; 839 return (1); 840 case 'b': 841 case 'B': 842 *new_key = 'b'|KEYC_META; 843 return (1); 844 case 'd': 845 *new_key = '\025'; 846 return (1); 847 case 'e': 848 case 'E': 849 case 'w': 850 case 'W': 851 *new_key = 'f'|KEYC_META; 852 return (1); 853 case 'p': 854 *new_key = '\031'; /* C-y */ 855 return (1); 856 case 'q': 857 *new_key = '\003'; /* C-c */ 858 return (1); 859 case 's': 860 case KEYC_DC: 861 case 'x': 862 *new_key = KEYC_DC; 863 return (1); 864 case KEYC_DOWN: 865 case 'j': 866 *new_key = KEYC_DOWN; 867 return (1); 868 case KEYC_LEFT: 869 case 'h': 870 *new_key = KEYC_LEFT; 871 return (1); 872 case 'a': 873 case KEYC_RIGHT: 874 case 'l': 875 *new_key = KEYC_RIGHT; 876 return (1); 877 case KEYC_UP: 878 case 'k': 879 *new_key = KEYC_UP; 880 return (1); 881 case '\010' /* C-h */: 882 case '\003' /* C-c */: 883 case '\n': 884 case '\r': 885 return (1); 886 } 887 return (0); 888 } 889 890 /* Paste into prompt. */ 891 static int 892 status_prompt_paste(struct client *c) 893 { 894 struct paste_buffer *pb; 895 const char *bufdata; 896 size_t size, n, bufsize; 897 u_int i; 898 struct utf8_data *ud, *udp; 899 enum utf8_state more; 900 901 size = utf8_strlen(c->prompt_buffer); 902 if (c->prompt_saved != NULL) { 903 ud = c->prompt_saved; 904 n = utf8_strlen(c->prompt_saved); 905 } else { 906 if ((pb = paste_get_top(NULL)) == NULL) 907 return (0); 908 bufdata = paste_buffer_data(pb, &bufsize); 909 ud = xreallocarray(NULL, bufsize + 1, sizeof *ud); 910 udp = ud; 911 for (i = 0; i != bufsize; /* nothing */) { 912 more = utf8_open(udp, bufdata[i]); 913 if (more == UTF8_MORE) { 914 while (++i != bufsize && more == UTF8_MORE) 915 more = utf8_append(udp, bufdata[i]); 916 if (more == UTF8_DONE) { 917 udp++; 918 continue; 919 } 920 i -= udp->have; 921 } 922 if (bufdata[i] <= 31 || bufdata[i] >= 127) 923 break; 924 utf8_set(udp, bufdata[i]); 925 udp++; 926 i++; 927 } 928 udp->size = 0; 929 n = udp - ud; 930 } 931 if (n == 0) 932 return (0); 933 934 c->prompt_buffer = xreallocarray(c->prompt_buffer, size + n + 1, 935 sizeof *c->prompt_buffer); 936 if (c->prompt_index == size) { 937 memcpy(c->prompt_buffer + c->prompt_index, ud, 938 n * sizeof *c->prompt_buffer); 939 c->prompt_index += n; 940 c->prompt_buffer[c->prompt_index].size = 0; 941 } else { 942 memmove(c->prompt_buffer + c->prompt_index + n, 943 c->prompt_buffer + c->prompt_index, 944 (size + 1 - c->prompt_index) * sizeof *c->prompt_buffer); 945 memcpy(c->prompt_buffer + c->prompt_index, ud, 946 n * sizeof *c->prompt_buffer); 947 c->prompt_index += n; 948 } 949 950 if (ud != c->prompt_saved) 951 free(ud); 952 return (1); 953 } 954 955 /* Finish completion. */ 956 static int 957 status_prompt_replace_complete(struct client *c, const char *s) 958 { 959 char word[64], *allocated = NULL; 960 size_t size, n, off, idx, used; 961 struct utf8_data *first, *last, *ud; 962 963 /* Work out where the cursor currently is. */ 964 idx = c->prompt_index; 965 if (idx != 0) 966 idx--; 967 size = utf8_strlen(c->prompt_buffer); 968 969 /* Find the word we are in. */ 970 first = &c->prompt_buffer[idx]; 971 while (first > c->prompt_buffer && !status_prompt_space(first)) 972 first--; 973 while (first->size != 0 && status_prompt_space(first)) 974 first++; 975 last = &c->prompt_buffer[idx]; 976 while (last->size != 0 && !status_prompt_space(last)) 977 last++; 978 while (last > c->prompt_buffer && status_prompt_space(last)) 979 last--; 980 if (last->size != 0) 981 last++; 982 if (last < first) 983 return (0); 984 if (s == NULL) { 985 used = 0; 986 for (ud = first; ud < last; ud++) { 987 if (used + ud->size >= sizeof word) 988 break; 989 memcpy(word + used, ud->data, ud->size); 990 used += ud->size; 991 } 992 if (ud != last) 993 return (0); 994 word[used] = '\0'; 995 } 996 997 /* Try to complete it. */ 998 if (s == NULL) { 999 allocated = status_prompt_complete(c, word, 1000 first - c->prompt_buffer); 1001 if (allocated == NULL) 1002 return (0); 1003 s = allocated; 1004 } 1005 1006 /* Trim out word. */ 1007 n = size - (last - c->prompt_buffer) + 1; /* with \0 */ 1008 memmove(first, last, n * sizeof *c->prompt_buffer); 1009 size -= last - first; 1010 1011 /* Insert the new word. */ 1012 size += strlen(s); 1013 off = first - c->prompt_buffer; 1014 c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 1, 1015 sizeof *c->prompt_buffer); 1016 first = c->prompt_buffer + off; 1017 memmove(first + strlen(s), first, n * sizeof *c->prompt_buffer); 1018 for (idx = 0; idx < strlen(s); idx++) 1019 utf8_set(&first[idx], s[idx]); 1020 c->prompt_index = (first - c->prompt_buffer) + strlen(s); 1021 1022 free(allocated); 1023 return (1); 1024 } 1025 1026 /* Handle keys in prompt. */ 1027 int 1028 status_prompt_key(struct client *c, key_code key) 1029 { 1030 struct options *oo = c->session->options; 1031 char *s, *cp, prefix = '='; 1032 const char *histstr, *ws = NULL, *keystring; 1033 size_t size, idx; 1034 struct utf8_data tmp; 1035 int keys; 1036 1037 if (c->prompt_flags & PROMPT_KEY) { 1038 keystring = key_string_lookup_key(key, 0); 1039 c->prompt_inputcb(c, c->prompt_data, keystring, 1); 1040 status_prompt_clear(c); 1041 return (0); 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_MASK_FLAGS; 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 1068 process_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 (status_prompt_replace_complete(c, NULL)) 1100 goto changed; 1101 break; 1102 case KEYC_BSPACE: 1103 case '\010': /* C-h */ 1104 if (c->prompt_index != 0) { 1105 if (c->prompt_index == size) 1106 c->prompt_buffer[--c->prompt_index].size = 0; 1107 else { 1108 memmove(c->prompt_buffer + c->prompt_index - 1, 1109 c->prompt_buffer + c->prompt_index, 1110 (size + 1 - c->prompt_index) * 1111 sizeof *c->prompt_buffer); 1112 c->prompt_index--; 1113 } 1114 goto changed; 1115 } 1116 break; 1117 case KEYC_DC: 1118 case '\004': /* C-d */ 1119 if (c->prompt_index != size) { 1120 memmove(c->prompt_buffer + c->prompt_index, 1121 c->prompt_buffer + c->prompt_index + 1, 1122 (size + 1 - c->prompt_index) * 1123 sizeof *c->prompt_buffer); 1124 goto changed; 1125 } 1126 break; 1127 case '\025': /* C-u */ 1128 c->prompt_buffer[0].size = 0; 1129 c->prompt_index = 0; 1130 goto changed; 1131 case '\013': /* C-k */ 1132 if (c->prompt_index < size) { 1133 c->prompt_buffer[c->prompt_index].size = 0; 1134 goto changed; 1135 } 1136 break; 1137 case '\027': /* C-w */ 1138 ws = options_get_string(oo, "word-separators"); 1139 idx = c->prompt_index; 1140 1141 /* Find a non-separator. */ 1142 while (idx != 0) { 1143 idx--; 1144 if (!status_prompt_in_list(ws, &c->prompt_buffer[idx])) 1145 break; 1146 } 1147 1148 /* Find the separator at the beginning of the word. */ 1149 while (idx != 0) { 1150 idx--; 1151 if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) { 1152 /* Go back to the word. */ 1153 idx++; 1154 break; 1155 } 1156 } 1157 1158 free(c->prompt_saved); 1159 c->prompt_saved = xcalloc(sizeof *c->prompt_buffer, 1160 (c->prompt_index - idx) + 1); 1161 memcpy(c->prompt_saved, c->prompt_buffer + idx, 1162 (c->prompt_index - idx) * sizeof *c->prompt_buffer); 1163 1164 memmove(c->prompt_buffer + idx, 1165 c->prompt_buffer + c->prompt_index, 1166 (size + 1 - c->prompt_index) * 1167 sizeof *c->prompt_buffer); 1168 memset(c->prompt_buffer + size - (c->prompt_index - idx), 1169 '\0', (c->prompt_index - idx) * sizeof *c->prompt_buffer); 1170 c->prompt_index = idx; 1171 1172 goto changed; 1173 case 'f'|KEYC_META: 1174 case KEYC_RIGHT|KEYC_CTRL: 1175 ws = options_get_string(oo, "word-separators"); 1176 1177 /* Find a word. */ 1178 while (c->prompt_index != size) { 1179 idx = ++c->prompt_index; 1180 if (!status_prompt_in_list(ws, &c->prompt_buffer[idx])) 1181 break; 1182 } 1183 1184 /* Find the separator at the end of the word. */ 1185 while (c->prompt_index != size) { 1186 idx = ++c->prompt_index; 1187 if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) 1188 break; 1189 } 1190 1191 /* Back up to the end-of-word like vi. */ 1192 if (options_get_number(oo, "status-keys") == MODEKEY_VI && 1193 c->prompt_index != 0) 1194 c->prompt_index--; 1195 1196 goto changed; 1197 case 'b'|KEYC_META: 1198 case KEYC_LEFT|KEYC_CTRL: 1199 ws = options_get_string(oo, "word-separators"); 1200 1201 /* Find a non-separator. */ 1202 while (c->prompt_index != 0) { 1203 idx = --c->prompt_index; 1204 if (!status_prompt_in_list(ws, &c->prompt_buffer[idx])) 1205 break; 1206 } 1207 1208 /* Find the separator at the beginning of the word. */ 1209 while (c->prompt_index != 0) { 1210 idx = --c->prompt_index; 1211 if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) { 1212 /* Go back to the word. */ 1213 c->prompt_index++; 1214 break; 1215 } 1216 } 1217 goto changed; 1218 case KEYC_UP: 1219 case '\020': /* C-p */ 1220 histstr = status_prompt_up_history(&c->prompt_hindex); 1221 if (histstr == NULL) 1222 break; 1223 free(c->prompt_buffer); 1224 c->prompt_buffer = utf8_fromcstr(histstr); 1225 c->prompt_index = utf8_strlen(c->prompt_buffer); 1226 goto changed; 1227 case KEYC_DOWN: 1228 case '\016': /* C-n */ 1229 histstr = status_prompt_down_history(&c->prompt_hindex); 1230 if (histstr == NULL) 1231 break; 1232 free(c->prompt_buffer); 1233 c->prompt_buffer = utf8_fromcstr(histstr); 1234 c->prompt_index = utf8_strlen(c->prompt_buffer); 1235 goto changed; 1236 case '\031': /* C-y */ 1237 if (status_prompt_paste(c)) 1238 goto changed; 1239 break; 1240 case '\024': /* C-t */ 1241 idx = c->prompt_index; 1242 if (idx < size) 1243 idx++; 1244 if (idx >= 2) { 1245 utf8_copy(&tmp, &c->prompt_buffer[idx - 2]); 1246 utf8_copy(&c->prompt_buffer[idx - 2], 1247 &c->prompt_buffer[idx - 1]); 1248 utf8_copy(&c->prompt_buffer[idx - 1], &tmp); 1249 c->prompt_index = idx; 1250 goto changed; 1251 } 1252 break; 1253 case '\r': 1254 case '\n': 1255 s = utf8_tocstr(c->prompt_buffer); 1256 if (*s != '\0') 1257 status_prompt_add_history(s); 1258 if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0) 1259 status_prompt_clear(c); 1260 free(s); 1261 break; 1262 case '\033': /* Escape */ 1263 case '\003': /* C-c */ 1264 case '\007': /* C-g */ 1265 if (c->prompt_inputcb(c, c->prompt_data, NULL, 1) == 0) 1266 status_prompt_clear(c); 1267 break; 1268 case '\022': /* C-r */ 1269 if (~c->prompt_flags & PROMPT_INCREMENTAL) 1270 break; 1271 if (c->prompt_buffer[0].size == 0) { 1272 prefix = '='; 1273 free (c->prompt_buffer); 1274 c->prompt_buffer = utf8_fromcstr(c->prompt_last); 1275 c->prompt_index = utf8_strlen(c->prompt_buffer); 1276 } else 1277 prefix = '-'; 1278 goto changed; 1279 case '\023': /* C-s */ 1280 if (~c->prompt_flags & PROMPT_INCREMENTAL) 1281 break; 1282 if (c->prompt_buffer[0].size == 0) { 1283 prefix = '='; 1284 free (c->prompt_buffer); 1285 c->prompt_buffer = utf8_fromcstr(c->prompt_last); 1286 c->prompt_index = utf8_strlen(c->prompt_buffer); 1287 } else 1288 prefix = '+'; 1289 goto changed; 1290 default: 1291 goto append_key; 1292 } 1293 1294 c->flags |= CLIENT_REDRAWSTATUS; 1295 return (0); 1296 1297 append_key: 1298 if (key <= 0x1f || key >= KEYC_BASE) 1299 return (0); 1300 if (key <= 0x7f) 1301 utf8_set(&tmp, key); 1302 else 1303 utf8_to_data(key, &tmp); 1304 1305 c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 2, 1306 sizeof *c->prompt_buffer); 1307 1308 if (c->prompt_index == size) { 1309 utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp); 1310 c->prompt_index++; 1311 c->prompt_buffer[c->prompt_index].size = 0; 1312 } else { 1313 memmove(c->prompt_buffer + c->prompt_index + 1, 1314 c->prompt_buffer + c->prompt_index, 1315 (size + 1 - c->prompt_index) * 1316 sizeof *c->prompt_buffer); 1317 utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp); 1318 c->prompt_index++; 1319 } 1320 1321 if (c->prompt_flags & PROMPT_SINGLE) { 1322 if (utf8_strlen(c->prompt_buffer) != 1) 1323 status_prompt_clear(c); 1324 else { 1325 s = utf8_tocstr(c->prompt_buffer); 1326 if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0) 1327 status_prompt_clear(c); 1328 free(s); 1329 } 1330 } 1331 1332 changed: 1333 c->flags |= CLIENT_REDRAWSTATUS; 1334 if (c->prompt_flags & PROMPT_INCREMENTAL) { 1335 s = utf8_tocstr(c->prompt_buffer); 1336 xasprintf(&cp, "%c%s", prefix, s); 1337 c->prompt_inputcb(c, c->prompt_data, cp, 0); 1338 free(cp); 1339 free(s); 1340 } 1341 return (0); 1342 } 1343 1344 /* Get previous line from the history. */ 1345 static const char * 1346 status_prompt_up_history(u_int *idx) 1347 { 1348 /* 1349 * History runs from 0 to size - 1. Index is from 0 to size. Zero is 1350 * empty. 1351 */ 1352 1353 if (status_prompt_hsize == 0 || *idx == status_prompt_hsize) 1354 return (NULL); 1355 (*idx)++; 1356 return (status_prompt_hlist[status_prompt_hsize - *idx]); 1357 } 1358 1359 /* Get next line from the history. */ 1360 static const char * 1361 status_prompt_down_history(u_int *idx) 1362 { 1363 if (status_prompt_hsize == 0 || *idx == 0) 1364 return (""); 1365 (*idx)--; 1366 if (*idx == 0) 1367 return (""); 1368 return (status_prompt_hlist[status_prompt_hsize - *idx]); 1369 } 1370 1371 /* Add line to the history. */ 1372 static void 1373 status_prompt_add_history(const char *line) 1374 { 1375 size_t size; 1376 1377 if (status_prompt_hsize > 0 && 1378 strcmp(status_prompt_hlist[status_prompt_hsize - 1], line) == 0) 1379 return; 1380 1381 if (status_prompt_hsize == PROMPT_HISTORY) { 1382 free(status_prompt_hlist[0]); 1383 1384 size = (PROMPT_HISTORY - 1) * sizeof *status_prompt_hlist; 1385 memmove(&status_prompt_hlist[0], &status_prompt_hlist[1], size); 1386 1387 status_prompt_hlist[status_prompt_hsize - 1] = xstrdup(line); 1388 return; 1389 } 1390 1391 status_prompt_hlist = xreallocarray(status_prompt_hlist, 1392 status_prompt_hsize + 1, sizeof *status_prompt_hlist); 1393 status_prompt_hlist[status_prompt_hsize++] = xstrdup(line); 1394 } 1395 1396 /* Build completion list. */ 1397 static char ** 1398 status_prompt_complete_list(u_int *size, const char *s, int at_start) 1399 { 1400 char **list = NULL; 1401 const char **layout, *value, *cp; 1402 const struct cmd_entry **cmdent; 1403 const struct options_table_entry *oe; 1404 size_t slen = strlen(s), valuelen; 1405 struct options_entry *o; 1406 struct options_array_item *a; 1407 const char *layouts[] = { 1408 "even-horizontal", "even-vertical", "main-horizontal", 1409 "main-vertical", "tiled", NULL 1410 }; 1411 1412 *size = 0; 1413 for (cmdent = cmd_table; *cmdent != NULL; cmdent++) { 1414 if (strncmp((*cmdent)->name, s, slen) == 0) { 1415 list = xreallocarray(list, (*size) + 1, sizeof *list); 1416 list[(*size)++] = xstrdup((*cmdent)->name); 1417 } 1418 if ((*cmdent)->alias != NULL && 1419 strncmp((*cmdent)->alias, s, slen) == 0) { 1420 list = xreallocarray(list, (*size) + 1, sizeof *list); 1421 list[(*size)++] = xstrdup((*cmdent)->alias); 1422 } 1423 } 1424 o = options_get_only(global_options, "command-alias"); 1425 if (o != NULL) { 1426 a = options_array_first(o); 1427 while (a != NULL) { 1428 value = options_array_item_value(a)->string; 1429 if ((cp = strchr(value, '=')) == NULL) 1430 goto next; 1431 valuelen = cp - value; 1432 if (slen > valuelen || strncmp(value, s, slen) != 0) 1433 goto next; 1434 1435 list = xreallocarray(list, (*size) + 1, sizeof *list); 1436 list[(*size)++] = xstrndup(value, valuelen); 1437 1438 next: 1439 a = options_array_next(a); 1440 } 1441 } 1442 if (at_start) 1443 return (list); 1444 1445 for (oe = options_table; oe->name != NULL; oe++) { 1446 if (strncmp(oe->name, s, slen) == 0) { 1447 list = xreallocarray(list, (*size) + 1, sizeof *list); 1448 list[(*size)++] = xstrdup(oe->name); 1449 } 1450 } 1451 for (layout = layouts; *layout != NULL; layout++) { 1452 if (strncmp(*layout, s, slen) == 0) { 1453 list = xreallocarray(list, (*size) + 1, sizeof *list); 1454 list[(*size)++] = xstrdup(*layout); 1455 } 1456 } 1457 return (list); 1458 } 1459 1460 /* Find longest prefix. */ 1461 static char * 1462 status_prompt_complete_prefix(char **list, u_int size) 1463 { 1464 char *out; 1465 u_int i; 1466 size_t j; 1467 1468 if (list == NULL || size == 0) 1469 return (NULL); 1470 out = xstrdup(list[0]); 1471 for (i = 1; i < size; i++) { 1472 j = strlen(list[i]); 1473 if (j > strlen(out)) 1474 j = strlen(out); 1475 for (; j > 0; j--) { 1476 if (out[j - 1] != list[i][j - 1]) 1477 out[j - 1] = '\0'; 1478 } 1479 } 1480 return (out); 1481 } 1482 1483 /* Complete word menu callback. */ 1484 static void 1485 status_prompt_menu_callback(__unused struct menu *menu, u_int idx, key_code key, 1486 void *data) 1487 { 1488 struct status_prompt_menu *spm = data; 1489 struct client *c = spm->c; 1490 u_int i; 1491 char *s; 1492 1493 if (key != KEYC_NONE) { 1494 idx += spm->start; 1495 if (spm->flag == '\0') 1496 s = xstrdup(spm->list[idx]); 1497 else 1498 xasprintf(&s, "-%c%s", spm->flag, spm->list[idx]); 1499 if (c->prompt_flags & PROMPT_WINDOW) { 1500 free(c->prompt_buffer); 1501 c->prompt_buffer = utf8_fromcstr(s); 1502 c->prompt_index = utf8_strlen(c->prompt_buffer); 1503 c->flags |= CLIENT_REDRAWSTATUS; 1504 } else if (status_prompt_replace_complete(c, s)) 1505 c->flags |= CLIENT_REDRAWSTATUS; 1506 free(s); 1507 } 1508 1509 for (i = 0; i < spm->size; i++) 1510 free(spm->list[i]); 1511 free(spm->list); 1512 } 1513 1514 /* Show complete word menu. */ 1515 static int 1516 status_prompt_complete_list_menu(struct client *c, char **list, u_int size, 1517 u_int offset, char flag) 1518 { 1519 struct menu *menu; 1520 struct menu_item item; 1521 struct status_prompt_menu *spm; 1522 u_int lines = status_line_size(c), height, i; 1523 u_int py; 1524 1525 if (size <= 1) 1526 return (0); 1527 if (c->tty.sy - lines < 3) 1528 return (0); 1529 1530 spm = xmalloc(sizeof *spm); 1531 spm->c = c; 1532 spm->size = size; 1533 spm->list = list; 1534 spm->flag = flag; 1535 1536 height = c->tty.sy - lines - 2; 1537 if (height > 10) 1538 height = 10; 1539 if (height > size) 1540 height = size; 1541 spm->start = size - height; 1542 1543 menu = menu_create(""); 1544 for (i = spm->start; i < size; i++) { 1545 item.name = list[i]; 1546 item.key = '0' + (i - spm->start); 1547 item.command = NULL; 1548 menu_add_item(menu, &item, NULL, NULL, NULL); 1549 } 1550 1551 if (options_get_number(c->session->options, "status-position") == 0) 1552 py = lines; 1553 else 1554 py = c->tty.sy - 3 - height; 1555 offset += utf8_cstrwidth(c->prompt_string); 1556 if (offset > 2) 1557 offset -= 2; 1558 else 1559 offset = 0; 1560 1561 if (menu_display(menu, MENU_NOMOUSE|MENU_TAB, NULL, offset, 1562 py, c, NULL, status_prompt_menu_callback, spm) != 0) { 1563 menu_free(menu); 1564 free(spm); 1565 return (0); 1566 } 1567 return (1); 1568 } 1569 1570 /* Show complete word menu. */ 1571 static char * 1572 status_prompt_complete_window_menu(struct client *c, struct session *s, 1573 const char *word, u_int offset, char flag) 1574 { 1575 struct menu *menu; 1576 struct menu_item item; 1577 struct status_prompt_menu *spm; 1578 struct winlink *wl; 1579 char **list = NULL, *tmp; 1580 u_int lines = status_line_size(c), height; 1581 u_int py, size = 0; 1582 1583 if (c->tty.sy - lines < 3) 1584 return (NULL); 1585 1586 spm = xmalloc(sizeof *spm); 1587 spm->c = c; 1588 spm->flag = flag; 1589 1590 height = c->tty.sy - lines - 2; 1591 if (height > 10) 1592 height = 10; 1593 spm->start = 0; 1594 1595 menu = menu_create(""); 1596 RB_FOREACH(wl, winlinks, &s->windows) { 1597 if (word != NULL && *word != '\0') { 1598 xasprintf(&tmp, "%d", wl->idx); 1599 if (strncmp(tmp, word, strlen(word)) != 0) { 1600 free(tmp); 1601 continue; 1602 } 1603 free(tmp); 1604 } 1605 1606 list = xreallocarray(list, size + 1, sizeof *list); 1607 if (c->prompt_flags & PROMPT_WINDOW) { 1608 xasprintf(&tmp, "%d (%s)", wl->idx, wl->window->name); 1609 xasprintf(&list[size++], "%d", wl->idx); 1610 } else { 1611 xasprintf(&tmp, "%s:%d (%s)", s->name, wl->idx, 1612 wl->window->name); 1613 xasprintf(&list[size++], "%s:%d", s->name, wl->idx); 1614 } 1615 item.name = tmp; 1616 item.key = '0' + size - 1; 1617 item.command = NULL; 1618 menu_add_item(menu, &item, NULL, NULL, NULL); 1619 free(tmp); 1620 1621 if (size == height) 1622 break; 1623 } 1624 if (size == 0) { 1625 menu_free(menu); 1626 return (NULL); 1627 } 1628 if (size == 1) { 1629 menu_free(menu); 1630 if (flag != '\0') { 1631 xasprintf(&tmp, "-%c%s", flag, list[0]); 1632 free(list[0]); 1633 } else 1634 tmp = list[0]; 1635 free(list); 1636 return (tmp); 1637 } 1638 if (height > size) 1639 height = size; 1640 1641 spm->size = size; 1642 spm->list = list; 1643 1644 if (options_get_number(c->session->options, "status-position") == 0) 1645 py = lines; 1646 else 1647 py = c->tty.sy - 3 - height; 1648 offset += utf8_cstrwidth(c->prompt_string); 1649 if (offset > 2) 1650 offset -= 2; 1651 else 1652 offset = 0; 1653 1654 if (menu_display(menu, MENU_NOMOUSE|MENU_TAB, NULL, offset, 1655 py, c, NULL, status_prompt_menu_callback, spm) != 0) { 1656 menu_free(menu); 1657 free(spm); 1658 return (NULL); 1659 } 1660 return (NULL); 1661 } 1662 1663 /* Sort complete list. */ 1664 static int 1665 status_prompt_complete_sort(const void *a, const void *b) 1666 { 1667 const char **aa = (const char **)a, **bb = (const char **)b; 1668 1669 return (strcmp(*aa, *bb)); 1670 } 1671 1672 /* Complete a session. */ 1673 static char * 1674 status_prompt_complete_session(char ***list, u_int *size, const char *s, 1675 char flag) 1676 { 1677 struct session *loop; 1678 char *out, *tmp, n[11]; 1679 1680 RB_FOREACH(loop, sessions, &sessions) { 1681 if (*s == '\0' || strncmp(loop->name, s, strlen(s)) == 0) { 1682 *list = xreallocarray(*list, (*size) + 2, 1683 sizeof **list); 1684 xasprintf(&(*list)[(*size)++], "%s:", loop->name); 1685 } else if (*s == '$') { 1686 xsnprintf(n, sizeof n, "%u", loop->id); 1687 if (s[1] == '\0' || 1688 strncmp(n, s + 1, strlen(s) - 1) == 0) { 1689 *list = xreallocarray(*list, (*size) + 2, 1690 sizeof **list); 1691 xasprintf(&(*list)[(*size)++], "$%s:", n); 1692 } 1693 } 1694 } 1695 out = status_prompt_complete_prefix(*list, *size); 1696 if (out != NULL && flag != '\0') { 1697 xasprintf(&tmp, "-%c%s", flag, out); 1698 free(out); 1699 out = tmp; 1700 } 1701 return (out); 1702 } 1703 1704 /* Complete word. */ 1705 static char * 1706 status_prompt_complete(struct client *c, const char *word, u_int offset) 1707 { 1708 struct session *session; 1709 const char *s, *colon; 1710 char **list = NULL, *copy = NULL, *out = NULL; 1711 char flag = '\0'; 1712 u_int size = 0, i; 1713 1714 if (*word == '\0' && 1715 ((c->prompt_flags & (PROMPT_TARGET|PROMPT_WINDOW)) == 0)) 1716 return (NULL); 1717 1718 if (((c->prompt_flags & (PROMPT_TARGET|PROMPT_WINDOW)) == 0) && 1719 strncmp(word, "-t", 2) != 0 && 1720 strncmp(word, "-s", 2) != 0) { 1721 list = status_prompt_complete_list(&size, word, offset == 0); 1722 if (size == 0) 1723 out = NULL; 1724 else if (size == 1) 1725 xasprintf(&out, "%s ", list[0]); 1726 else 1727 out = status_prompt_complete_prefix(list, size); 1728 goto found; 1729 } 1730 1731 if (c->prompt_flags & (PROMPT_TARGET|PROMPT_WINDOW)) { 1732 s = word; 1733 flag = '\0'; 1734 } else { 1735 s = word + 2; 1736 flag = word[1]; 1737 offset += 2; 1738 } 1739 1740 /* If this is a window completion, open the window menu. */ 1741 if (c->prompt_flags & PROMPT_WINDOW) { 1742 out = status_prompt_complete_window_menu(c, c->session, s, 1743 offset, '\0'); 1744 goto found; 1745 } 1746 colon = strchr(s, ':'); 1747 1748 /* If there is no colon, complete as a session. */ 1749 if (colon == NULL) { 1750 out = status_prompt_complete_session(&list, &size, s, flag); 1751 goto found; 1752 } 1753 1754 /* If there is a colon but no period, find session and show a menu. */ 1755 if (strchr(colon + 1, '.') == NULL) { 1756 if (*s == ':') 1757 session = c->session; 1758 else { 1759 copy = xstrdup(s); 1760 *strchr(copy, ':') = '\0'; 1761 session = session_find(copy); 1762 free(copy); 1763 if (session == NULL) 1764 goto found; 1765 } 1766 out = status_prompt_complete_window_menu(c, session, colon + 1, 1767 offset, flag); 1768 if (out == NULL) 1769 return (NULL); 1770 } 1771 1772 found: 1773 if (size != 0) { 1774 qsort(list, size, sizeof *list, status_prompt_complete_sort); 1775 for (i = 0; i < size; i++) 1776 log_debug("complete %u: %s", i, list[i]); 1777 } 1778 1779 if (out != NULL && strcmp(word, out) == 0) { 1780 free(out); 1781 out = NULL; 1782 } 1783 if (out != NULL || 1784 !status_prompt_complete_list_menu(c, list, size, offset, flag)) { 1785 for (i = 0; i < size; i++) 1786 free(list[i]); 1787 free(list); 1788 } 1789 return (out); 1790 } 1791