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