1 /* $OpenBSD$ */ 2 3 /* 4 * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott (at) 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 *, u_int); 37 static const char *status_prompt_down_history(u_int *, u_int); 38 static void status_prompt_add_history(const char *, u_int); 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 static const char *prompt_type_strings[] = { 53 "command", 54 "search", 55 "target", 56 "window-target" 57 }; 58 59 /* Status prompt history. */ 60 char **status_prompt_hlist[PROMPT_NTYPES]; 61 u_int status_prompt_hsize[PROMPT_NTYPES]; 62 63 /* Find the history file to load/save from/to. */ 64 static char * 65 status_prompt_find_history_file(void) 66 { 67 const char *home, *history_file; 68 char *path; 69 70 history_file = options_get_string(global_options, "history-file"); 71 if (*history_file == '\0') 72 return (NULL); 73 if (*history_file == '/') 74 return (xstrdup(history_file)); 75 76 if (history_file[0] != '~' || history_file[1] != '/') 77 return (NULL); 78 if ((home = find_home()) == NULL) 79 return (NULL); 80 xasprintf(&path, "%s%s", home, history_file + 1); 81 return (path); 82 } 83 84 /* Add loaded history item to the appropriate list. */ 85 static void 86 status_prompt_add_typed_history(char *line) 87 { 88 char *typestr; 89 enum prompt_type type = PROMPT_TYPE_INVALID; 90 91 typestr = strsep(&line, ":"); 92 if (line != NULL) 93 type = status_prompt_type(typestr); 94 if (type == PROMPT_TYPE_INVALID) { 95 /* 96 * Invalid types are not expected, but this provides backward 97 * compatibility with old history files. 98 */ 99 if (line != NULL) 100 *(--line) = ':'; 101 status_prompt_add_history(typestr, PROMPT_TYPE_COMMAND); 102 } else 103 status_prompt_add_history(line, type); 104 } 105 106 /* Load status prompt history from file. */ 107 void 108 status_prompt_load_history(void) 109 { 110 FILE *f; 111 char *history_file, *line, *tmp; 112 size_t length; 113 114 if ((history_file = status_prompt_find_history_file()) == NULL) 115 return; 116 log_debug("loading history from %s", history_file); 117 118 f = fopen(history_file, "r"); 119 if (f == NULL) { 120 log_debug("%s: %s", history_file, strerror(errno)); 121 free(history_file); 122 return; 123 } 124 free(history_file); 125 126 for (;;) { 127 if ((line = fgetln(f, &length)) == NULL) 128 break; 129 130 if (length > 0) { 131 if (line[length - 1] == '\n') { 132 line[length - 1] = '\0'; 133 status_prompt_add_typed_history(line); 134 } else { 135 tmp = xmalloc(length + 1); 136 memcpy(tmp, line, length); 137 tmp[length] = '\0'; 138 status_prompt_add_typed_history(tmp); 139 free(tmp); 140 } 141 } 142 } 143 fclose(f); 144 } 145 146 /* Save status prompt history to file. */ 147 void 148 status_prompt_save_history(void) 149 { 150 FILE *f; 151 u_int i, type; 152 char *history_file; 153 154 if ((history_file = status_prompt_find_history_file()) == NULL) 155 return; 156 log_debug("saving history to %s", history_file); 157 158 f = fopen(history_file, "w"); 159 if (f == NULL) { 160 log_debug("%s: %s", history_file, strerror(errno)); 161 free(history_file); 162 return; 163 } 164 free(history_file); 165 166 for (type = 0; type < PROMPT_NTYPES; type++) { 167 for (i = 0; i < status_prompt_hsize[type]; i++) { 168 fputs(prompt_type_strings[type], f); 169 fputc(':', f); 170 fputs(status_prompt_hlist[type][i], f); 171 fputc('\n', f); 172 } 173 } 174 fclose(f); 175 176 } 177 178 /* Status timer callback. */ 179 static void 180 status_timer_callback(__unused int fd, __unused short events, void *arg) 181 { 182 struct client *c = arg; 183 struct session *s = c->session; 184 struct timeval tv; 185 186 evtimer_del(&c->status.timer); 187 188 if (s == NULL) 189 return; 190 191 if (c->message_string == NULL && c->prompt_string == NULL) 192 c->flags |= CLIENT_REDRAWSTATUS; 193 194 timerclear(&tv); 195 tv.tv_sec = options_get_number(s->options, "status-interval"); 196 197 if (tv.tv_sec != 0) 198 evtimer_add(&c->status.timer, &tv); 199 log_debug("client %p, status interval %d", c, (int)tv.tv_sec); 200 } 201 202 /* Start status timer for client. */ 203 void 204 status_timer_start(struct client *c) 205 { 206 struct session *s = c->session; 207 208 if (event_initialized(&c->status.timer)) 209 evtimer_del(&c->status.timer); 210 else 211 evtimer_set(&c->status.timer, status_timer_callback, c); 212 213 if (s != NULL && options_get_number(s->options, "status")) 214 status_timer_callback(-1, 0, c); 215 } 216 217 /* Start status timer for all clients. */ 218 void 219 status_timer_start_all(void) 220 { 221 struct client *c; 222 223 TAILQ_FOREACH(c, &clients, entry) 224 status_timer_start(c); 225 } 226 227 /* Update status cache. */ 228 void 229 status_update_cache(struct session *s) 230 { 231 s->statuslines = options_get_number(s->options, "status"); 232 if (s->statuslines == 0) 233 s->statusat = -1; 234 else if (options_get_number(s->options, "status-position") == 0) 235 s->statusat = 0; 236 else 237 s->statusat = 1; 238 } 239 240 /* Get screen line of status line. -1 means off. */ 241 int 242 status_at_line(struct client *c) 243 { 244 struct session *s = c->session; 245 246 if (c->flags & (CLIENT_STATUSOFF|CLIENT_CONTROL)) 247 return (-1); 248 if (s->statusat != 1) 249 return (s->statusat); 250 return (c->tty.sy - status_line_size(c)); 251 } 252 253 /* Get size of status line for client's session. 0 means off. */ 254 u_int 255 status_line_size(struct client *c) 256 { 257 struct session *s = c->session; 258 259 if (c->flags & (CLIENT_STATUSOFF|CLIENT_CONTROL)) 260 return (0); 261 if (s == NULL) 262 return (options_get_number(global_s_options, "status")); 263 return (s->statuslines); 264 } 265 266 /* Get the prompt line number for client's session. 1 means at the bottom. */ 267 u_int 268 status_prompt_line_at(struct client *c) 269 { 270 struct session *s = c->session; 271 u_int line, lines; 272 273 lines = status_line_size(c); 274 if (lines == 0) 275 return (0); 276 line = options_get_number(s->options, "message-line"); 277 if (line >= lines) 278 return (lines - 1); 279 return (line); 280 } 281 282 /* Get window at window list position. */ 283 struct style_range * 284 status_get_range(struct client *c, u_int x, u_int y) 285 { 286 struct status_line *sl = &c->status; 287 struct style_range *sr; 288 289 if (y >= nitems(sl->entries)) 290 return (NULL); 291 TAILQ_FOREACH(sr, &sl->entries[y].ranges, entry) { 292 if (x >= sr->start && x < sr->end) 293 return (sr); 294 } 295 return (NULL); 296 } 297 298 /* Free all ranges. */ 299 static void 300 status_free_ranges(struct style_ranges *srs) 301 { 302 struct style_range *sr, *sr1; 303 304 TAILQ_FOREACH_SAFE(sr, srs, entry, sr1) { 305 TAILQ_REMOVE(srs, sr, entry); 306 free(sr); 307 } 308 } 309 310 /* Save old status line. */ 311 static void 312 status_push_screen(struct client *c) 313 { 314 struct status_line *sl = &c->status; 315 316 if (sl->active == &sl->screen) { 317 sl->active = xmalloc(sizeof *sl->active); 318 screen_init(sl->active, c->tty.sx, status_line_size(c), 0); 319 } 320 sl->references++; 321 } 322 323 /* Restore old status line. */ 324 static void 325 status_pop_screen(struct client *c) 326 { 327 struct status_line *sl = &c->status; 328 329 if (--sl->references == 0) { 330 screen_free(sl->active); 331 free(sl->active); 332 sl->active = &sl->screen; 333 } 334 } 335 336 /* Initialize status line. */ 337 void 338 status_init(struct client *c) 339 { 340 struct status_line *sl = &c->status; 341 u_int i; 342 343 for (i = 0; i < nitems(sl->entries); i++) 344 TAILQ_INIT(&sl->entries[i].ranges); 345 346 screen_init(&sl->screen, c->tty.sx, 1, 0); 347 sl->active = &sl->screen; 348 } 349 350 /* Free status line. */ 351 void 352 status_free(struct client *c) 353 { 354 struct status_line *sl = &c->status; 355 u_int i; 356 357 for (i = 0; i < nitems(sl->entries); i++) { 358 status_free_ranges(&sl->entries[i].ranges); 359 free((void *)sl->entries[i].expanded); 360 } 361 362 if (event_initialized(&sl->timer)) 363 evtimer_del(&sl->timer); 364 365 if (sl->active != &sl->screen) { 366 screen_free(sl->active); 367 free(sl->active); 368 } 369 screen_free(&sl->screen); 370 } 371 372 /* Draw status line for client. */ 373 int 374 status_redraw(struct client *c) 375 { 376 struct status_line *sl = &c->status; 377 struct status_line_entry *sle; 378 struct session *s = c->session; 379 struct screen_write_ctx ctx; 380 struct grid_cell gc; 381 u_int lines, i, n, width = c->tty.sx; 382 int flags, force = 0, changed = 0, fg, bg; 383 struct options_entry *o; 384 union options_value *ov; 385 struct format_tree *ft; 386 char *expanded; 387 388 log_debug("%s enter", __func__); 389 390 /* Shouldn't get here if not the active screen. */ 391 if (sl->active != &sl->screen) 392 fatalx("not the active screen"); 393 394 /* No status line? */ 395 lines = status_line_size(c); 396 if (c->tty.sy == 0 || lines == 0) 397 return (1); 398 399 /* Create format tree. */ 400 flags = FORMAT_STATUS; 401 if (c->flags & CLIENT_STATUSFORCE) 402 flags |= FORMAT_FORCE; 403 ft = format_create(c, NULL, FORMAT_NONE, flags); 404 format_defaults(ft, c, NULL, NULL, NULL); 405 406 /* Set up default colour. */ 407 style_apply(&gc, s->options, "status-style", ft); 408 fg = options_get_number(s->options, "status-fg"); 409 if (!COLOUR_DEFAULT(fg)) 410 gc.fg = fg; 411 bg = options_get_number(s->options, "status-bg"); 412 if (!COLOUR_DEFAULT(bg)) 413 gc.bg = bg; 414 if (!grid_cells_equal(&gc, &sl->style)) { 415 force = 1; 416 memcpy(&sl->style, &gc, sizeof sl->style); 417 } 418 419 /* Resize the target screen. */ 420 if (screen_size_x(&sl->screen) != width || 421 screen_size_y(&sl->screen) != lines) { 422 screen_resize(&sl->screen, width, lines, 0); 423 changed = force = 1; 424 } 425 screen_write_start(&ctx, &sl->screen); 426 427 /* Write the status lines. */ 428 o = options_get(s->options, "status-format"); 429 if (o == NULL) { 430 for (n = 0; n < width * lines; n++) 431 screen_write_putc(&ctx, &gc, ' '); 432 } else { 433 for (i = 0; i < lines; i++) { 434 screen_write_cursormove(&ctx, 0, i, 0); 435 436 ov = options_array_get(o, i); 437 if (ov == NULL) { 438 for (n = 0; n < width; n++) 439 screen_write_putc(&ctx, &gc, ' '); 440 continue; 441 } 442 sle = &sl->entries[i]; 443 444 expanded = format_expand_time(ft, ov->string); 445 if (!force && 446 sle->expanded != NULL && 447 strcmp(expanded, sle->expanded) == 0) { 448 free(expanded); 449 continue; 450 } 451 changed = 1; 452 453 for (n = 0; n < width; n++) 454 screen_write_putc(&ctx, &gc, ' '); 455 screen_write_cursormove(&ctx, 0, i, 0); 456 457 status_free_ranges(&sle->ranges); 458 format_draw(&ctx, &gc, width, expanded, &sle->ranges, 459 0); 460 461 free(sle->expanded); 462 sle->expanded = expanded; 463 } 464 } 465 screen_write_stop(&ctx); 466 467 /* Free the format tree. */ 468 format_free(ft); 469 470 /* Return if the status line has changed. */ 471 log_debug("%s exit: force=%d, changed=%d", __func__, force, changed); 472 return (force || changed); 473 } 474 475 /* Set a status line message. */ 476 void 477 status_message_set(struct client *c, int delay, int ignore_styles, 478 int ignore_keys, int no_freeze, const char *fmt, ...) 479 { 480 struct timeval tv; 481 va_list ap; 482 char *s; 483 484 va_start(ap, fmt); 485 xvasprintf(&s, fmt, ap); 486 va_end(ap); 487 488 log_debug("%s: %s", __func__, s); 489 490 if (c == NULL) { 491 server_add_message("message: %s", s); 492 free(s); 493 return; 494 } 495 496 status_message_clear(c); 497 status_push_screen(c); 498 c->message_string = s; 499 server_add_message("%s message: %s", c->name, s); 500 501 /* 502 * With delay -1, the display-time option is used; zero means wait for 503 * key press; more than zero is the actual delay time in milliseconds. 504 */ 505 if (delay == -1) 506 delay = options_get_number(c->session->options, "display-time"); 507 if (delay > 0) { 508 tv.tv_sec = delay / 1000; 509 tv.tv_usec = (delay % 1000) * 1000L; 510 511 if (event_initialized(&c->message_timer)) 512 evtimer_del(&c->message_timer); 513 evtimer_set(&c->message_timer, status_message_callback, c); 514 515 evtimer_add(&c->message_timer, &tv); 516 } 517 518 if (delay != 0) 519 c->message_ignore_keys = ignore_keys; 520 c->message_ignore_styles = ignore_styles; 521 522 if (!no_freeze) 523 c->tty.flags |= TTY_FREEZE; 524 c->tty.flags |= TTY_NOCURSOR; 525 c->flags |= CLIENT_REDRAWSTATUS; 526 } 527 528 /* Clear status line message. */ 529 void 530 status_message_clear(struct client *c) 531 { 532 if (c->message_string == NULL) 533 return; 534 535 free(c->message_string); 536 c->message_string = NULL; 537 538 if (c->prompt_string == NULL) 539 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE); 540 c->flags |= CLIENT_ALLREDRAWFLAGS; /* was frozen and may have changed */ 541 542 status_pop_screen(c); 543 } 544 545 /* Clear status line message after timer expires. */ 546 static void 547 status_message_callback(__unused int fd, __unused short event, void *data) 548 { 549 struct client *c = data; 550 551 status_message_clear(c); 552 } 553 554 /* Draw client message on status line of present else on last line. */ 555 int 556 status_message_redraw(struct client *c) 557 { 558 struct status_line *sl = &c->status; 559 struct screen_write_ctx ctx; 560 struct session *s = c->session; 561 struct screen old_screen; 562 size_t len; 563 u_int lines, offset, messageline; 564 struct grid_cell gc; 565 struct format_tree *ft; 566 567 if (c->tty.sx == 0 || c->tty.sy == 0) 568 return (0); 569 memcpy(&old_screen, sl->active, sizeof old_screen); 570 571 lines = status_line_size(c); 572 if (lines <= 1) 573 lines = 1; 574 screen_init(sl->active, c->tty.sx, lines, 0); 575 576 messageline = status_prompt_line_at(c); 577 if (messageline > lines - 1) 578 messageline = lines - 1; 579 580 len = screen_write_strlen("%s", c->message_string); 581 if (len > c->tty.sx) 582 len = c->tty.sx; 583 584 ft = format_create_defaults(NULL, c, NULL, NULL, NULL); 585 style_apply(&gc, s->options, "message-style", ft); 586 format_free(ft); 587 588 screen_write_start(&ctx, sl->active); 589 screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines); 590 screen_write_cursormove(&ctx, 0, messageline, 0); 591 for (offset = 0; offset < c->tty.sx; offset++) 592 screen_write_putc(&ctx, &gc, ' '); 593 screen_write_cursormove(&ctx, 0, messageline, 0); 594 if (c->message_ignore_styles) 595 screen_write_nputs(&ctx, len, &gc, "%s", c->message_string); 596 else 597 format_draw(&ctx, &gc, c->tty.sx, c->message_string, NULL, 0); 598 screen_write_stop(&ctx); 599 600 if (grid_compare(sl->active->grid, old_screen.grid) == 0) { 601 screen_free(&old_screen); 602 return (0); 603 } 604 screen_free(&old_screen); 605 return (1); 606 } 607 608 /* Accept prompt immediately. */ 609 static enum cmd_retval 610 status_prompt_accept(__unused struct cmdq_item *item, void *data) 611 { 612 struct client *c = data; 613 614 if (c->prompt_string != NULL) { 615 c->prompt_inputcb(c, c->prompt_data, "y", 1); 616 status_prompt_clear(c); 617 } 618 return (CMD_RETURN_NORMAL); 619 } 620 621 /* Enable status line prompt. */ 622 void 623 status_prompt_set(struct client *c, struct cmd_find_state *fs, 624 const char *msg, const char *input, prompt_input_cb inputcb, 625 prompt_free_cb freecb, void *data, int flags, enum prompt_type prompt_type) 626 { 627 struct format_tree *ft; 628 char *tmp; 629 630 server_client_clear_overlay(c); 631 632 if (fs != NULL) 633 ft = format_create_from_state(NULL, c, fs); 634 else 635 ft = format_create_defaults(NULL, c, NULL, NULL, NULL); 636 637 if (input == NULL) 638 input = ""; 639 640 status_message_clear(c); 641 status_prompt_clear(c); 642 status_push_screen(c); 643 644 c->prompt_formats = ft; 645 c->prompt_string = xstrdup (msg); 646 647 if (flags & PROMPT_NOFORMAT) 648 tmp = xstrdup(input); 649 else 650 tmp = format_expand_time(ft, input); 651 if (flags & PROMPT_INCREMENTAL) { 652 c->prompt_last = xstrdup(tmp); 653 c->prompt_buffer = utf8_fromcstr(""); 654 } else { 655 c->prompt_last = NULL; 656 c->prompt_buffer = utf8_fromcstr(tmp); 657 } 658 c->prompt_index = utf8_strlen(c->prompt_buffer); 659 free(tmp); 660 661 c->prompt_inputcb = inputcb; 662 c->prompt_freecb = freecb; 663 c->prompt_data = data; 664 665 memset(c->prompt_hindex, 0, sizeof c->prompt_hindex); 666 667 c->prompt_flags = flags; 668 c->prompt_type = prompt_type; 669 c->prompt_mode = PROMPT_ENTRY; 670 671 if (~flags & PROMPT_INCREMENTAL) 672 c->tty.flags |= TTY_FREEZE; 673 c->flags |= CLIENT_REDRAWSTATUS; 674 675 if (flags & PROMPT_INCREMENTAL) 676 c->prompt_inputcb(c, c->prompt_data, "=", 0); 677 678 if ((flags & PROMPT_SINGLE) && (flags & PROMPT_ACCEPT)) 679 cmdq_append(c, cmdq_get_callback(status_prompt_accept, c)); 680 } 681 682 /* Remove status line prompt. */ 683 void 684 status_prompt_clear(struct client *c) 685 { 686 if (c->prompt_string == NULL) 687 return; 688 689 if (c->prompt_freecb != NULL && c->prompt_data != NULL) 690 c->prompt_freecb(c->prompt_data); 691 692 free(c->prompt_last); 693 c->prompt_last = NULL; 694 695 format_free(c->prompt_formats); 696 c->prompt_formats = NULL; 697 698 free(c->prompt_string); 699 c->prompt_string = NULL; 700 701 free(c->prompt_buffer); 702 c->prompt_buffer = NULL; 703 704 free(c->prompt_saved); 705 c->prompt_saved = NULL; 706 707 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE); 708 c->flags |= CLIENT_ALLREDRAWFLAGS; /* was frozen and may have changed */ 709 710 status_pop_screen(c); 711 } 712 713 /* Update status line prompt with a new prompt string. */ 714 void 715 status_prompt_update(struct client *c, const char *msg, const char *input) 716 { 717 char *tmp; 718 719 free(c->prompt_string); 720 c->prompt_string = xstrdup(msg); 721 722 free(c->prompt_buffer); 723 tmp = format_expand_time(c->prompt_formats, input); 724 c->prompt_buffer = utf8_fromcstr(tmp); 725 c->prompt_index = utf8_strlen(c->prompt_buffer); 726 free(tmp); 727 728 memset(c->prompt_hindex, 0, sizeof c->prompt_hindex); 729 730 c->flags |= CLIENT_REDRAWSTATUS; 731 } 732 733 /* Redraw character. Return 1 if can continue redrawing, 0 otherwise. */ 734 static int 735 status_prompt_redraw_character(struct screen_write_ctx *ctx, u_int offset, 736 u_int pwidth, u_int *width, struct grid_cell *gc, 737 const struct utf8_data *ud) 738 { 739 u_char ch; 740 741 if (*width < offset) { 742 *width += ud->width; 743 return (1); 744 } 745 if (*width >= offset + pwidth) 746 return (0); 747 *width += ud->width; 748 if (*width > offset + pwidth) 749 return (0); 750 751 ch = *ud->data; 752 if (ud->size == 1 && (ch <= 0x1f || ch == 0x7f)) { 753 gc->data.data[0] = '^'; 754 gc->data.data[1] = (ch == 0x7f) ? '?' : ch|0x40; 755 gc->data.size = gc->data.have = 2; 756 gc->data.width = 2; 757 } else 758 utf8_copy(&gc->data, ud); 759 screen_write_cell(ctx, gc); 760 return (1); 761 } 762 763 /* 764 * Redraw quote indicator '^' if necessary. Return 1 if can continue redrawing, 765 * 0 otherwise. 766 */ 767 static int 768 status_prompt_redraw_quote(const struct client *c, u_int pcursor, 769 struct screen_write_ctx *ctx, u_int offset, u_int pwidth, u_int *width, 770 struct grid_cell *gc) 771 { 772 struct utf8_data ud; 773 774 if (c->prompt_flags & PROMPT_QUOTENEXT && ctx->s->cx == pcursor + 1) { 775 utf8_set(&ud, '^'); 776 return (status_prompt_redraw_character(ctx, offset, pwidth, 777 width, gc, &ud)); 778 } 779 return (1); 780 } 781 782 /* Draw client prompt on status line of present else on last line. */ 783 int 784 status_prompt_redraw(struct client *c) 785 { 786 struct status_line *sl = &c->status; 787 struct screen_write_ctx ctx; 788 struct session *s = c->session; 789 struct screen old_screen; 790 u_int i, lines, offset, left, start, width, n; 791 u_int pcursor, pwidth, promptline; 792 struct grid_cell gc; 793 struct format_tree *ft = c->prompt_formats; 794 char *prompt, *tmp; 795 796 if (c->tty.sx == 0 || c->tty.sy == 0) 797 return (0); 798 memcpy(&old_screen, sl->active, sizeof old_screen); 799 800 lines = status_line_size(c); 801 if (lines <= 1) 802 lines = 1; 803 screen_init(sl->active, c->tty.sx, lines, 0); 804 805 n = options_get_number(s->options, "prompt-cursor-colour"); 806 sl->active->default_ccolour = n; 807 n = options_get_number(s->options, "prompt-cursor-style"); 808 screen_set_cursor_style(n, &sl->active->default_cstyle, 809 &sl->active->default_mode); 810 811 promptline = status_prompt_line_at(c); 812 if (promptline > lines - 1) 813 promptline = lines - 1; 814 815 if (c->prompt_mode == PROMPT_COMMAND) 816 style_apply(&gc, s->options, "message-command-style", ft); 817 else 818 style_apply(&gc, s->options, "message-style", ft); 819 820 tmp = utf8_tocstr(c->prompt_buffer); 821 format_add(c->prompt_formats, "prompt-input", "%s", tmp); 822 prompt = format_expand_time(c->prompt_formats, c->prompt_string); 823 free (tmp); 824 825 start = format_width(prompt); 826 if (start > c->tty.sx) 827 start = c->tty.sx; 828 829 screen_write_start(&ctx, sl->active); 830 screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines); 831 screen_write_cursormove(&ctx, 0, promptline, 0); 832 for (offset = 0; offset < c->tty.sx; offset++) 833 screen_write_putc(&ctx, &gc, ' '); 834 screen_write_cursormove(&ctx, 0, promptline, 0); 835 format_draw(&ctx, &gc, start, prompt, NULL, 0); 836 screen_write_cursormove(&ctx, start, promptline, 0); 837 838 left = c->tty.sx - start; 839 if (left == 0) 840 goto finished; 841 842 pcursor = utf8_strwidth(c->prompt_buffer, c->prompt_index); 843 pwidth = utf8_strwidth(c->prompt_buffer, -1); 844 if (c->prompt_flags & PROMPT_QUOTENEXT) 845 pwidth++; 846 if (pcursor >= left) { 847 /* 848 * The cursor would be outside the screen so start drawing 849 * with it on the right. 850 */ 851 offset = (pcursor - left) + 1; 852 pwidth = left; 853 } else 854 offset = 0; 855 if (pwidth > left) 856 pwidth = left; 857 c->prompt_cursor = start + pcursor - offset; 858 859 width = 0; 860 for (i = 0; c->prompt_buffer[i].size != 0; i++) { 861 if (!status_prompt_redraw_quote(c, pcursor, &ctx, offset, 862 pwidth, &width, &gc)) 863 break; 864 if (!status_prompt_redraw_character(&ctx, offset, pwidth, 865 &width, &gc, &c->prompt_buffer[i])) 866 break; 867 } 868 status_prompt_redraw_quote(c, pcursor, &ctx, offset, pwidth, &width, 869 &gc); 870 871 finished: 872 screen_write_stop(&ctx); 873 874 if (grid_compare(sl->active->grid, old_screen.grid) == 0) { 875 screen_free(&old_screen); 876 return (0); 877 } 878 screen_free(&old_screen); 879 return (1); 880 } 881 882 /* Is this a separator? */ 883 static int 884 status_prompt_in_list(const char *ws, const struct utf8_data *ud) 885 { 886 if (ud->size != 1 || ud->width != 1) 887 return (0); 888 return (strchr(ws, *ud->data) != NULL); 889 } 890 891 /* Is this a space? */ 892 static int 893 status_prompt_space(const struct utf8_data *ud) 894 { 895 if (ud->size != 1 || ud->width != 1) 896 return (0); 897 return (*ud->data == ' '); 898 } 899 900 /* 901 * Translate key from vi to emacs. Return 0 to drop key, 1 to process the key 902 * as an emacs key; return 2 to append to the buffer. 903 */ 904 static int 905 status_prompt_translate_key(struct client *c, key_code key, key_code *new_key) 906 { 907 if (c->prompt_mode == PROMPT_ENTRY) { 908 switch (key) { 909 case 'a'|KEYC_CTRL: 910 case 'c'|KEYC_CTRL: 911 case 'e'|KEYC_CTRL: 912 case 'g'|KEYC_CTRL: 913 case 'h'|KEYC_CTRL: 914 case '\011': /* Tab */ 915 case 'k'|KEYC_CTRL: 916 case 'n'|KEYC_CTRL: 917 case 'p'|KEYC_CTRL: 918 case 't'|KEYC_CTRL: 919 case 'u'|KEYC_CTRL: 920 case 'v'|KEYC_CTRL: 921 case 'w'|KEYC_CTRL: 922 case 'y'|KEYC_CTRL: 923 case '\n': 924 case '\r': 925 case KEYC_LEFT|KEYC_CTRL: 926 case KEYC_RIGHT|KEYC_CTRL: 927 case KEYC_BSPACE: 928 case KEYC_DC: 929 case KEYC_DOWN: 930 case KEYC_END: 931 case KEYC_HOME: 932 case KEYC_LEFT: 933 case KEYC_RIGHT: 934 case KEYC_UP: 935 *new_key = key; 936 return (1); 937 case '\033': /* Escape */ 938 c->prompt_mode = PROMPT_COMMAND; 939 c->flags |= CLIENT_REDRAWSTATUS; 940 return (0); 941 } 942 *new_key = key; 943 return (2); 944 } 945 946 switch (key) { 947 case KEYC_BSPACE: 948 *new_key = KEYC_LEFT; 949 return (1); 950 case 'A': 951 case 'I': 952 case 'C': 953 case 's': 954 case 'a': 955 c->prompt_mode = PROMPT_ENTRY; 956 c->flags |= CLIENT_REDRAWSTATUS; 957 break; /* switch mode and... */ 958 case 'S': 959 c->prompt_mode = PROMPT_ENTRY; 960 c->flags |= CLIENT_REDRAWSTATUS; 961 *new_key = 'u'|KEYC_CTRL; 962 return (1); 963 case 'i': 964 case '\033': /* Escape */ 965 c->prompt_mode = PROMPT_ENTRY; 966 c->flags |= CLIENT_REDRAWSTATUS; 967 return (0); 968 } 969 970 switch (key) { 971 case 'A': 972 case '$': 973 *new_key = KEYC_END; 974 return (1); 975 case 'I': 976 case '0': 977 case '^': 978 *new_key = KEYC_HOME; 979 return (1); 980 case 'C': 981 case 'D': 982 *new_key = 'k'|KEYC_CTRL; 983 return (1); 984 case KEYC_BSPACE: 985 case 'X': 986 *new_key = KEYC_BSPACE; 987 return (1); 988 case 'b': 989 *new_key = 'b'|KEYC_META; 990 return (1); 991 case 'B': 992 *new_key = 'B'|KEYC_VI; 993 return (1); 994 case 'd': 995 *new_key = 'u'|KEYC_CTRL; 996 return (1); 997 case 'e': 998 *new_key = 'e'|KEYC_VI; 999 return (1); 1000 case 'E': 1001 *new_key = 'E'|KEYC_VI; 1002 return (1); 1003 case 'w': 1004 *new_key = 'w'|KEYC_VI; 1005 return (1); 1006 case 'W': 1007 *new_key = 'W'|KEYC_VI; 1008 return (1); 1009 case 'p': 1010 *new_key = 'y'|KEYC_CTRL; 1011 return (1); 1012 case 'q': 1013 *new_key = 'c'|KEYC_CTRL; 1014 return (1); 1015 case 's': 1016 case KEYC_DC: 1017 case 'x': 1018 *new_key = KEYC_DC; 1019 return (1); 1020 case KEYC_DOWN: 1021 case 'j': 1022 *new_key = KEYC_DOWN; 1023 return (1); 1024 case KEYC_LEFT: 1025 case 'h': 1026 *new_key = KEYC_LEFT; 1027 return (1); 1028 case 'a': 1029 case KEYC_RIGHT: 1030 case 'l': 1031 *new_key = KEYC_RIGHT; 1032 return (1); 1033 case KEYC_UP: 1034 case 'k': 1035 *new_key = KEYC_UP; 1036 return (1); 1037 case 'h'|KEYC_CTRL: 1038 case 'c'|KEYC_CTRL: 1039 case '\n': 1040 case '\r': 1041 return (1); 1042 } 1043 return (0); 1044 } 1045 1046 /* Paste into prompt. */ 1047 static int 1048 status_prompt_paste(struct client *c) 1049 { 1050 struct paste_buffer *pb; 1051 const char *bufdata; 1052 size_t size, n, bufsize; 1053 u_int i; 1054 struct utf8_data *ud, *udp; 1055 enum utf8_state more; 1056 1057 size = utf8_strlen(c->prompt_buffer); 1058 if (c->prompt_saved != NULL) { 1059 ud = c->prompt_saved; 1060 n = utf8_strlen(c->prompt_saved); 1061 } else { 1062 if ((pb = paste_get_top(NULL)) == NULL) 1063 return (0); 1064 bufdata = paste_buffer_data(pb, &bufsize); 1065 ud = udp = xreallocarray(NULL, bufsize + 1, sizeof *ud); 1066 for (i = 0; i != bufsize; /* nothing */) { 1067 more = utf8_open(udp, bufdata[i]); 1068 if (more == UTF8_MORE) { 1069 while (++i != bufsize && more == UTF8_MORE) 1070 more = utf8_append(udp, bufdata[i]); 1071 if (more == UTF8_DONE) { 1072 udp++; 1073 continue; 1074 } 1075 i -= udp->have; 1076 } 1077 if (bufdata[i] <= 31 || bufdata[i] >= 127) 1078 break; 1079 utf8_set(udp, bufdata[i]); 1080 udp++; 1081 i++; 1082 } 1083 udp->size = 0; 1084 n = udp - ud; 1085 } 1086 if (n != 0) { 1087 c->prompt_buffer = xreallocarray(c->prompt_buffer, size + n + 1, 1088 sizeof *c->prompt_buffer); 1089 if (c->prompt_index == size) { 1090 memcpy(c->prompt_buffer + c->prompt_index, ud, 1091 n * sizeof *c->prompt_buffer); 1092 c->prompt_index += n; 1093 c->prompt_buffer[c->prompt_index].size = 0; 1094 } else { 1095 memmove(c->prompt_buffer + c->prompt_index + n, 1096 c->prompt_buffer + c->prompt_index, 1097 (size + 1 - c->prompt_index) * 1098 sizeof *c->prompt_buffer); 1099 memcpy(c->prompt_buffer + c->prompt_index, ud, 1100 n * sizeof *c->prompt_buffer); 1101 c->prompt_index += n; 1102 } 1103 } 1104 if (ud != c->prompt_saved) 1105 free(ud); 1106 return (1); 1107 } 1108 1109 /* Finish completion. */ 1110 static int 1111 status_prompt_replace_complete(struct client *c, const char *s) 1112 { 1113 char word[64], *allocated = NULL; 1114 size_t size, n, off, idx, used; 1115 struct utf8_data *first, *last, *ud; 1116 1117 /* Work out where the cursor currently is. */ 1118 idx = c->prompt_index; 1119 if (idx != 0) 1120 idx--; 1121 size = utf8_strlen(c->prompt_buffer); 1122 1123 /* Find the word we are in. */ 1124 first = &c->prompt_buffer[idx]; 1125 while (first > c->prompt_buffer && !status_prompt_space(first)) 1126 first--; 1127 while (first->size != 0 && status_prompt_space(first)) 1128 first++; 1129 last = &c->prompt_buffer[idx]; 1130 while (last->size != 0 && !status_prompt_space(last)) 1131 last++; 1132 while (last > c->prompt_buffer && status_prompt_space(last)) 1133 last--; 1134 if (last->size != 0) 1135 last++; 1136 if (last < first) 1137 return (0); 1138 if (s == NULL) { 1139 used = 0; 1140 for (ud = first; ud < last; ud++) { 1141 if (used + ud->size >= sizeof word) 1142 break; 1143 memcpy(word + used, ud->data, ud->size); 1144 used += ud->size; 1145 } 1146 if (ud != last) 1147 return (0); 1148 word[used] = '\0'; 1149 } 1150 1151 /* Try to complete it. */ 1152 if (s == NULL) { 1153 allocated = status_prompt_complete(c, word, 1154 first - c->prompt_buffer); 1155 if (allocated == NULL) 1156 return (0); 1157 s = allocated; 1158 } 1159 1160 /* Trim out word. */ 1161 n = size - (last - c->prompt_buffer) + 1; /* with \0 */ 1162 memmove(first, last, n * sizeof *c->prompt_buffer); 1163 size -= last - first; 1164 1165 /* Insert the new word. */ 1166 size += strlen(s); 1167 off = first - c->prompt_buffer; 1168 c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 1, 1169 sizeof *c->prompt_buffer); 1170 first = c->prompt_buffer + off; 1171 memmove(first + strlen(s), first, n * sizeof *c->prompt_buffer); 1172 for (idx = 0; idx < strlen(s); idx++) 1173 utf8_set(&first[idx], s[idx]); 1174 c->prompt_index = (first - c->prompt_buffer) + strlen(s); 1175 1176 free(allocated); 1177 return (1); 1178 } 1179 1180 /* Prompt forward to the next beginning of a word. */ 1181 static void 1182 status_prompt_forward_word(struct client *c, size_t size, int vi, 1183 const char *separators) 1184 { 1185 size_t idx = c->prompt_index; 1186 int word_is_separators; 1187 1188 /* In emacs mode, skip until the first non-whitespace character. */ 1189 if (!vi) 1190 while (idx != size && 1191 status_prompt_space(&c->prompt_buffer[idx])) 1192 idx++; 1193 1194 /* Can't move forward if we're already at the end. */ 1195 if (idx == size) { 1196 c->prompt_index = idx; 1197 return; 1198 } 1199 1200 /* Determine the current character class (separators or not). */ 1201 word_is_separators = status_prompt_in_list(separators, 1202 &c->prompt_buffer[idx]) && 1203 !status_prompt_space(&c->prompt_buffer[idx]); 1204 1205 /* Skip ahead until the first space or opposite character class. */ 1206 do { 1207 idx++; 1208 if (status_prompt_space(&c->prompt_buffer[idx])) { 1209 /* In vi mode, go to the start of the next word. */ 1210 if (vi) 1211 while (idx != size && 1212 status_prompt_space(&c->prompt_buffer[idx])) 1213 idx++; 1214 break; 1215 } 1216 } while (idx != size && word_is_separators == status_prompt_in_list( 1217 separators, &c->prompt_buffer[idx])); 1218 1219 c->prompt_index = idx; 1220 } 1221 1222 /* Prompt forward to the next end of a word. */ 1223 static void 1224 status_prompt_end_word(struct client *c, size_t size, const char *separators) 1225 { 1226 size_t idx = c->prompt_index; 1227 int word_is_separators; 1228 1229 /* Can't move forward if we're already at the end. */ 1230 if (idx == size) 1231 return; 1232 1233 /* Find the next word. */ 1234 do { 1235 idx++; 1236 if (idx == size) { 1237 c->prompt_index = idx; 1238 return; 1239 } 1240 } while (status_prompt_space(&c->prompt_buffer[idx])); 1241 1242 /* Determine the character class (separators or not). */ 1243 word_is_separators = status_prompt_in_list(separators, 1244 &c->prompt_buffer[idx]); 1245 1246 /* Skip ahead until the next space or opposite character class. */ 1247 do { 1248 idx++; 1249 if (idx == size) 1250 break; 1251 } while (!status_prompt_space(&c->prompt_buffer[idx]) && 1252 word_is_separators == status_prompt_in_list(separators, 1253 &c->prompt_buffer[idx])); 1254 1255 /* Back up to the previous character to stop at the end of the word. */ 1256 c->prompt_index = idx - 1; 1257 } 1258 1259 /* Prompt backward to the previous beginning of a word. */ 1260 static void 1261 status_prompt_backward_word(struct client *c, const char *separators) 1262 { 1263 size_t idx = c->prompt_index; 1264 int word_is_separators; 1265 1266 /* Find non-whitespace. */ 1267 while (idx != 0) { 1268 --idx; 1269 if (!status_prompt_space(&c->prompt_buffer[idx])) 1270 break; 1271 } 1272 word_is_separators = status_prompt_in_list(separators, 1273 &c->prompt_buffer[idx]); 1274 1275 /* Find the character before the beginning of the word. */ 1276 while (idx != 0) { 1277 --idx; 1278 if (status_prompt_space(&c->prompt_buffer[idx]) || 1279 word_is_separators != status_prompt_in_list(separators, 1280 &c->prompt_buffer[idx])) { 1281 /* Go back to the word. */ 1282 idx++; 1283 break; 1284 } 1285 } 1286 c->prompt_index = idx; 1287 } 1288 1289 /* Handle keys in prompt. */ 1290 int 1291 status_prompt_key(struct client *c, key_code key) 1292 { 1293 struct options *oo = c->session->options; 1294 char *s, *cp, prefix = '='; 1295 const char *histstr, *separators = NULL, *keystring; 1296 size_t size, idx; 1297 struct utf8_data tmp; 1298 int keys, word_is_separators; 1299 1300 if (c->prompt_flags & PROMPT_KEY) { 1301 keystring = key_string_lookup_key(key, 0); 1302 c->prompt_inputcb(c, c->prompt_data, keystring, 1); 1303 status_prompt_clear(c); 1304 return (0); 1305 } 1306 size = utf8_strlen(c->prompt_buffer); 1307 1308 if (c->prompt_flags & PROMPT_NUMERIC) { 1309 if (key >= '0' && key <= '9') 1310 goto append_key; 1311 s = utf8_tocstr(c->prompt_buffer); 1312 c->prompt_inputcb(c, c->prompt_data, s, 1); 1313 status_prompt_clear(c); 1314 free(s); 1315 return (1); 1316 } 1317 key &= ~KEYC_MASK_FLAGS; 1318 1319 if (c->prompt_flags & (PROMPT_SINGLE|PROMPT_QUOTENEXT)) { 1320 if ((key & KEYC_MASK_KEY) == KEYC_BSPACE) 1321 key = 0x7f; 1322 else if ((key & KEYC_MASK_KEY) > 0x7f) { 1323 if (!KEYC_IS_UNICODE(key)) 1324 return (0); 1325 key &= KEYC_MASK_KEY; 1326 } else 1327 key &= (key & KEYC_CTRL) ? 0x1f : KEYC_MASK_KEY; 1328 c->prompt_flags &= ~PROMPT_QUOTENEXT; 1329 goto append_key; 1330 } 1331 1332 keys = options_get_number(c->session->options, "status-keys"); 1333 if (keys == MODEKEY_VI) { 1334 switch (status_prompt_translate_key(c, key, &key)) { 1335 case 1: 1336 goto process_key; 1337 case 2: 1338 goto append_key; 1339 default: 1340 return (0); 1341 } 1342 } 1343 1344 process_key: 1345 switch (key) { 1346 case KEYC_LEFT: 1347 case 'b'|KEYC_CTRL: 1348 if (c->prompt_index > 0) { 1349 c->prompt_index--; 1350 break; 1351 } 1352 break; 1353 case KEYC_RIGHT: 1354 case 'f'|KEYC_CTRL: 1355 if (c->prompt_index < size) { 1356 c->prompt_index++; 1357 break; 1358 } 1359 break; 1360 case KEYC_HOME: 1361 case 'a'|KEYC_CTRL: 1362 if (c->prompt_index != 0) { 1363 c->prompt_index = 0; 1364 break; 1365 } 1366 break; 1367 case KEYC_END: 1368 case 'e'|KEYC_CTRL: 1369 if (c->prompt_index != size) { 1370 c->prompt_index = size; 1371 break; 1372 } 1373 break; 1374 case '\011': /* Tab */ 1375 if (status_prompt_replace_complete(c, NULL)) 1376 goto changed; 1377 break; 1378 case KEYC_BSPACE: 1379 case 'h'|KEYC_CTRL: 1380 if (c->prompt_index != 0) { 1381 if (c->prompt_index == size) 1382 c->prompt_buffer[--c->prompt_index].size = 0; 1383 else { 1384 memmove(c->prompt_buffer + c->prompt_index - 1, 1385 c->prompt_buffer + c->prompt_index, 1386 (size + 1 - c->prompt_index) * 1387 sizeof *c->prompt_buffer); 1388 c->prompt_index--; 1389 } 1390 goto changed; 1391 } 1392 break; 1393 case KEYC_DC: 1394 case 'd'|KEYC_CTRL: 1395 if (c->prompt_index != size) { 1396 memmove(c->prompt_buffer + c->prompt_index, 1397 c->prompt_buffer + c->prompt_index + 1, 1398 (size + 1 - c->prompt_index) * 1399 sizeof *c->prompt_buffer); 1400 goto changed; 1401 } 1402 break; 1403 case 'u'|KEYC_CTRL: 1404 c->prompt_buffer[0].size = 0; 1405 c->prompt_index = 0; 1406 goto changed; 1407 case 'k'|KEYC_CTRL: 1408 if (c->prompt_index < size) { 1409 c->prompt_buffer[c->prompt_index].size = 0; 1410 goto changed; 1411 } 1412 break; 1413 case 'w'|KEYC_CTRL: 1414 separators = options_get_string(oo, "word-separators"); 1415 idx = c->prompt_index; 1416 1417 /* Find non-whitespace. */ 1418 while (idx != 0) { 1419 idx--; 1420 if (!status_prompt_space(&c->prompt_buffer[idx])) 1421 break; 1422 } 1423 word_is_separators = status_prompt_in_list(separators, 1424 &c->prompt_buffer[idx]); 1425 1426 /* Find the character before the beginning of the word. */ 1427 while (idx != 0) { 1428 idx--; 1429 if (status_prompt_space(&c->prompt_buffer[idx]) || 1430 word_is_separators != status_prompt_in_list( 1431 separators, &c->prompt_buffer[idx])) { 1432 /* Go back to the word. */ 1433 idx++; 1434 break; 1435 } 1436 } 1437 1438 free(c->prompt_saved); 1439 c->prompt_saved = xcalloc(sizeof *c->prompt_buffer, 1440 (c->prompt_index - idx) + 1); 1441 memcpy(c->prompt_saved, c->prompt_buffer + idx, 1442 (c->prompt_index - idx) * sizeof *c->prompt_buffer); 1443 1444 memmove(c->prompt_buffer + idx, 1445 c->prompt_buffer + c->prompt_index, 1446 (size + 1 - c->prompt_index) * 1447 sizeof *c->prompt_buffer); 1448 memset(c->prompt_buffer + size - (c->prompt_index - idx), 1449 '\0', (c->prompt_index - idx) * sizeof *c->prompt_buffer); 1450 c->prompt_index = idx; 1451 1452 goto changed; 1453 case KEYC_RIGHT|KEYC_CTRL: 1454 case 'f'|KEYC_META: 1455 separators = options_get_string(oo, "word-separators"); 1456 status_prompt_forward_word(c, size, 0, separators); 1457 goto changed; 1458 case 'E'|KEYC_VI: 1459 status_prompt_end_word(c, size, ""); 1460 goto changed; 1461 case 'e'|KEYC_VI: 1462 separators = options_get_string(oo, "word-separators"); 1463 status_prompt_end_word(c, size, separators); 1464 goto changed; 1465 case 'W'|KEYC_VI: 1466 status_prompt_forward_word(c, size, 1, ""); 1467 goto changed; 1468 case 'w'|KEYC_VI: 1469 separators = options_get_string(oo, "word-separators"); 1470 status_prompt_forward_word(c, size, 1, separators); 1471 goto changed; 1472 case 'B'|KEYC_VI: 1473 status_prompt_backward_word(c, ""); 1474 goto changed; 1475 case KEYC_LEFT|KEYC_CTRL: 1476 case 'b'|KEYC_META: 1477 separators = options_get_string(oo, "word-separators"); 1478 status_prompt_backward_word(c, separators); 1479 goto changed; 1480 case KEYC_UP: 1481 case 'p'|KEYC_CTRL: 1482 histstr = status_prompt_up_history(c->prompt_hindex, 1483 c->prompt_type); 1484 if (histstr == NULL) 1485 break; 1486 free(c->prompt_buffer); 1487 c->prompt_buffer = utf8_fromcstr(histstr); 1488 c->prompt_index = utf8_strlen(c->prompt_buffer); 1489 goto changed; 1490 case KEYC_DOWN: 1491 case 'n'|KEYC_CTRL: 1492 histstr = status_prompt_down_history(c->prompt_hindex, 1493 c->prompt_type); 1494 if (histstr == NULL) 1495 break; 1496 free(c->prompt_buffer); 1497 c->prompt_buffer = utf8_fromcstr(histstr); 1498 c->prompt_index = utf8_strlen(c->prompt_buffer); 1499 goto changed; 1500 case 'y'|KEYC_CTRL: 1501 if (status_prompt_paste(c)) 1502 goto changed; 1503 break; 1504 case 't'|KEYC_CTRL: 1505 idx = c->prompt_index; 1506 if (idx < size) 1507 idx++; 1508 if (idx >= 2) { 1509 utf8_copy(&tmp, &c->prompt_buffer[idx - 2]); 1510 utf8_copy(&c->prompt_buffer[idx - 2], 1511 &c->prompt_buffer[idx - 1]); 1512 utf8_copy(&c->prompt_buffer[idx - 1], &tmp); 1513 c->prompt_index = idx; 1514 goto changed; 1515 } 1516 break; 1517 case '\r': 1518 case '\n': 1519 s = utf8_tocstr(c->prompt_buffer); 1520 if (*s != '\0') 1521 status_prompt_add_history(s, c->prompt_type); 1522 if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0) 1523 status_prompt_clear(c); 1524 free(s); 1525 break; 1526 case '\033': /* Escape */ 1527 case 'c'|KEYC_CTRL: 1528 case 'g'|KEYC_CTRL: 1529 if (c->prompt_inputcb(c, c->prompt_data, NULL, 1) == 0) 1530 status_prompt_clear(c); 1531 break; 1532 case 'r'|KEYC_CTRL: 1533 if (~c->prompt_flags & PROMPT_INCREMENTAL) 1534 break; 1535 if (c->prompt_buffer[0].size == 0) { 1536 prefix = '='; 1537 free(c->prompt_buffer); 1538 c->prompt_buffer = utf8_fromcstr(c->prompt_last); 1539 c->prompt_index = utf8_strlen(c->prompt_buffer); 1540 } else 1541 prefix = '-'; 1542 goto changed; 1543 case 's'|KEYC_CTRL: 1544 if (~c->prompt_flags & PROMPT_INCREMENTAL) 1545 break; 1546 if (c->prompt_buffer[0].size == 0) { 1547 prefix = '='; 1548 free(c->prompt_buffer); 1549 c->prompt_buffer = utf8_fromcstr(c->prompt_last); 1550 c->prompt_index = utf8_strlen(c->prompt_buffer); 1551 } else 1552 prefix = '+'; 1553 goto changed; 1554 case 'v'|KEYC_CTRL: 1555 c->prompt_flags |= PROMPT_QUOTENEXT; 1556 break; 1557 default: 1558 goto append_key; 1559 } 1560 1561 c->flags |= CLIENT_REDRAWSTATUS; 1562 return (0); 1563 1564 append_key: 1565 if (key <= 0x7f) { 1566 utf8_set(&tmp, key); 1567 if (key <= 0x1f || key == 0x7f) 1568 tmp.width = 2; 1569 } else if (KEYC_IS_UNICODE(key)) 1570 utf8_to_data(key, &tmp); 1571 else 1572 return (0); 1573 1574 c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 2, 1575 sizeof *c->prompt_buffer); 1576 1577 if (c->prompt_index == size) { 1578 utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp); 1579 c->prompt_index++; 1580 c->prompt_buffer[c->prompt_index].size = 0; 1581 } else { 1582 memmove(c->prompt_buffer + c->prompt_index + 1, 1583 c->prompt_buffer + c->prompt_index, 1584 (size + 1 - c->prompt_index) * 1585 sizeof *c->prompt_buffer); 1586 utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp); 1587 c->prompt_index++; 1588 } 1589 1590 if (c->prompt_flags & PROMPT_SINGLE) { 1591 if (utf8_strlen(c->prompt_buffer) != 1) 1592 status_prompt_clear(c); 1593 else { 1594 s = utf8_tocstr(c->prompt_buffer); 1595 if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0) 1596 status_prompt_clear(c); 1597 free(s); 1598 } 1599 } 1600 1601 changed: 1602 c->flags |= CLIENT_REDRAWSTATUS; 1603 if (c->prompt_flags & PROMPT_INCREMENTAL) { 1604 s = utf8_tocstr(c->prompt_buffer); 1605 xasprintf(&cp, "%c%s", prefix, s); 1606 c->prompt_inputcb(c, c->prompt_data, cp, 0); 1607 free(cp); 1608 free(s); 1609 } 1610 return (0); 1611 } 1612 1613 /* Get previous line from the history. */ 1614 static const char * 1615 status_prompt_up_history(u_int *idx, u_int type) 1616 { 1617 /* 1618 * History runs from 0 to size - 1. Index is from 0 to size. Zero is 1619 * empty. 1620 */ 1621 1622 if (status_prompt_hsize[type] == 0 || 1623 idx[type] == status_prompt_hsize[type]) 1624 return (NULL); 1625 idx[type]++; 1626 return (status_prompt_hlist[type][status_prompt_hsize[type] - idx[type]]); 1627 } 1628 1629 /* Get next line from the history. */ 1630 static const char * 1631 status_prompt_down_history(u_int *idx, u_int type) 1632 { 1633 if (status_prompt_hsize[type] == 0 || idx[type] == 0) 1634 return (""); 1635 idx[type]--; 1636 if (idx[type] == 0) 1637 return (""); 1638 return (status_prompt_hlist[type][status_prompt_hsize[type] - idx[type]]); 1639 } 1640 1641 /* Add line to the history. */ 1642 static void 1643 status_prompt_add_history(const char *line, u_int type) 1644 { 1645 u_int i, oldsize, newsize, freecount, hlimit, new = 1; 1646 size_t movesize; 1647 1648 oldsize = status_prompt_hsize[type]; 1649 if (oldsize > 0 && 1650 strcmp(status_prompt_hlist[type][oldsize - 1], line) == 0) 1651 new = 0; 1652 1653 hlimit = options_get_number(global_options, "prompt-history-limit"); 1654 if (hlimit > oldsize) { 1655 if (new == 0) 1656 return; 1657 newsize = oldsize + new; 1658 } else { 1659 newsize = hlimit; 1660 freecount = oldsize + new - newsize; 1661 if (freecount > oldsize) 1662 freecount = oldsize; 1663 if (freecount == 0) 1664 return; 1665 for (i = 0; i < freecount; i++) 1666 free(status_prompt_hlist[type][i]); 1667 movesize = (oldsize - freecount) * 1668 sizeof *status_prompt_hlist[type]; 1669 if (movesize > 0) { 1670 memmove(&status_prompt_hlist[type][0], 1671 &status_prompt_hlist[type][freecount], movesize); 1672 } 1673 } 1674 1675 if (newsize == 0) { 1676 free(status_prompt_hlist[type]); 1677 status_prompt_hlist[type] = NULL; 1678 } else if (newsize != oldsize) { 1679 status_prompt_hlist[type] = 1680 xreallocarray(status_prompt_hlist[type], newsize, 1681 sizeof *status_prompt_hlist[type]); 1682 } 1683 1684 if (new == 1 && newsize > 0) 1685 status_prompt_hlist[type][newsize - 1] = xstrdup(line); 1686 status_prompt_hsize[type] = newsize; 1687 } 1688 1689 /* Add to completion list. */ 1690 static void 1691 status_prompt_add_list(char ***list, u_int *size, const char *s) 1692 { 1693 u_int i; 1694 1695 for (i = 0; i < *size; i++) { 1696 if (strcmp((*list)[i], s) == 0) 1697 return; 1698 } 1699 *list = xreallocarray(*list, (*size) + 1, sizeof **list); 1700 (*list)[(*size)++] = xstrdup(s); 1701 } 1702 1703 /* Build completion list. */ 1704 static char ** 1705 status_prompt_complete_list(u_int *size, const char *s, int at_start) 1706 { 1707 char **list = NULL, *tmp; 1708 const char **layout, *value, *cp; 1709 const struct cmd_entry **cmdent; 1710 const struct options_table_entry *oe; 1711 size_t slen = strlen(s), valuelen; 1712 struct options_entry *o; 1713 struct options_array_item *a; 1714 const char *layouts[] = { 1715 "even-horizontal", "even-vertical", 1716 "main-horizontal", "main-horizontal-mirrored", 1717 "main-vertical", "main-vertical-mirrored", "tiled", NULL 1718 }; 1719 1720 *size = 0; 1721 for (cmdent = cmd_table; *cmdent != NULL; cmdent++) { 1722 if (strncmp((*cmdent)->name, s, slen) == 0) 1723 status_prompt_add_list(&list, size, (*cmdent)->name); 1724 if ((*cmdent)->alias != NULL && 1725 strncmp((*cmdent)->alias, s, slen) == 0) 1726 status_prompt_add_list(&list, size, (*cmdent)->alias); 1727 } 1728 o = options_get_only(global_options, "command-alias"); 1729 if (o != NULL) { 1730 a = options_array_first(o); 1731 while (a != NULL) { 1732 value = options_array_item_value(a)->string; 1733 if ((cp = strchr(value, '=')) == NULL) 1734 goto next; 1735 valuelen = cp - value; 1736 if (slen > valuelen || strncmp(value, s, slen) != 0) 1737 goto next; 1738 1739 xasprintf(&tmp, "%.*s", (int)valuelen, value); 1740 status_prompt_add_list(&list, size, tmp); 1741 free(tmp); 1742 1743 next: 1744 a = options_array_next(a); 1745 } 1746 } 1747 if (at_start) 1748 return (list); 1749 for (oe = options_table; oe->name != NULL; oe++) { 1750 if (strncmp(oe->name, s, slen) == 0) 1751 status_prompt_add_list(&list, size, oe->name); 1752 } 1753 for (layout = layouts; *layout != NULL; layout++) { 1754 if (strncmp(*layout, s, slen) == 0) 1755 status_prompt_add_list(&list, size, *layout); 1756 } 1757 return (list); 1758 } 1759 1760 /* Find longest prefix. */ 1761 static char * 1762 status_prompt_complete_prefix(char **list, u_int size) 1763 { 1764 char *out; 1765 u_int i; 1766 size_t j; 1767 1768 if (list == NULL || size == 0) 1769 return (NULL); 1770 out = xstrdup(list[0]); 1771 for (i = 1; i < size; i++) { 1772 j = strlen(list[i]); 1773 if (j > strlen(out)) 1774 j = strlen(out); 1775 for (; j > 0; j--) { 1776 if (out[j - 1] != list[i][j - 1]) 1777 out[j - 1] = '\0'; 1778 } 1779 } 1780 return (out); 1781 } 1782 1783 /* Complete word menu callback. */ 1784 static void 1785 status_prompt_menu_callback(__unused struct menu *menu, u_int idx, key_code key, 1786 void *data) 1787 { 1788 struct status_prompt_menu *spm = data; 1789 struct client *c = spm->c; 1790 u_int i; 1791 char *s; 1792 1793 if (key != KEYC_NONE) { 1794 idx += spm->start; 1795 if (spm->flag == '\0') 1796 s = xstrdup(spm->list[idx]); 1797 else 1798 xasprintf(&s, "-%c%s", spm->flag, spm->list[idx]); 1799 if (c->prompt_type == PROMPT_TYPE_WINDOW_TARGET) { 1800 free(c->prompt_buffer); 1801 c->prompt_buffer = utf8_fromcstr(s); 1802 c->prompt_index = utf8_strlen(c->prompt_buffer); 1803 c->flags |= CLIENT_REDRAWSTATUS; 1804 } else if (status_prompt_replace_complete(c, s)) 1805 c->flags |= CLIENT_REDRAWSTATUS; 1806 free(s); 1807 } 1808 1809 for (i = 0; i < spm->size; i++) 1810 free(spm->list[i]); 1811 free(spm->list); 1812 } 1813 1814 /* Show complete word menu. */ 1815 static int 1816 status_prompt_complete_list_menu(struct client *c, char **list, u_int size, 1817 u_int offset, char flag) 1818 { 1819 struct menu *menu; 1820 struct menu_item item; 1821 struct status_prompt_menu *spm; 1822 u_int lines = status_line_size(c), height, i; 1823 u_int py; 1824 1825 if (size <= 1) 1826 return (0); 1827 if (c->tty.sy - lines < 3) 1828 return (0); 1829 1830 spm = xmalloc(sizeof *spm); 1831 spm->c = c; 1832 spm->size = size; 1833 spm->list = list; 1834 spm->flag = flag; 1835 1836 height = c->tty.sy - lines - 2; 1837 if (height > 10) 1838 height = 10; 1839 if (height > size) 1840 height = size; 1841 spm->start = size - height; 1842 1843 menu = menu_create(""); 1844 for (i = spm->start; i < size; i++) { 1845 item.name = list[i]; 1846 item.key = '0' + (i - spm->start); 1847 item.command = NULL; 1848 menu_add_item(menu, &item, NULL, c, NULL); 1849 } 1850 1851 if (options_get_number(c->session->options, "status-position") == 0) 1852 py = lines; 1853 else 1854 py = c->tty.sy - 3 - height; 1855 offset += utf8_cstrwidth(c->prompt_string); 1856 if (offset > 2) 1857 offset -= 2; 1858 else 1859 offset = 0; 1860 1861 if (menu_display(menu, MENU_NOMOUSE|MENU_TAB, 0, NULL, offset, py, c, 1862 BOX_LINES_DEFAULT, NULL, NULL, NULL, NULL, 1863 status_prompt_menu_callback, spm) != 0) { 1864 menu_free(menu); 1865 free(spm); 1866 return (0); 1867 } 1868 return (1); 1869 } 1870 1871 /* Show complete word menu. */ 1872 static char * 1873 status_prompt_complete_window_menu(struct client *c, struct session *s, 1874 const char *word, u_int offset, char flag) 1875 { 1876 struct menu *menu; 1877 struct menu_item item; 1878 struct status_prompt_menu *spm; 1879 struct winlink *wl; 1880 char **list = NULL, *tmp; 1881 u_int lines = status_line_size(c), height; 1882 u_int py, size = 0; 1883 1884 if (c->tty.sy - lines < 3) 1885 return (NULL); 1886 1887 spm = xmalloc(sizeof *spm); 1888 spm->c = c; 1889 spm->flag = flag; 1890 1891 height = c->tty.sy - lines - 2; 1892 if (height > 10) 1893 height = 10; 1894 spm->start = 0; 1895 1896 menu = menu_create(""); 1897 RB_FOREACH(wl, winlinks, &s->windows) { 1898 if (word != NULL && *word != '\0') { 1899 xasprintf(&tmp, "%d", wl->idx); 1900 if (strncmp(tmp, word, strlen(word)) != 0) { 1901 free(tmp); 1902 continue; 1903 } 1904 free(tmp); 1905 } 1906 1907 list = xreallocarray(list, size + 1, sizeof *list); 1908 if (c->prompt_type == PROMPT_TYPE_WINDOW_TARGET) { 1909 xasprintf(&tmp, "%d (%s)", wl->idx, wl->window->name); 1910 xasprintf(&list[size++], "%d", wl->idx); 1911 } else { 1912 xasprintf(&tmp, "%s:%d (%s)", s->name, wl->idx, 1913 wl->window->name); 1914 xasprintf(&list[size++], "%s:%d", s->name, wl->idx); 1915 } 1916 item.name = tmp; 1917 item.key = '0' + size - 1; 1918 item.command = NULL; 1919 menu_add_item(menu, &item, NULL, c, NULL); 1920 free(tmp); 1921 1922 if (size == height) 1923 break; 1924 } 1925 if (size == 0) { 1926 menu_free(menu); 1927 free(spm); 1928 return (NULL); 1929 } 1930 if (size == 1) { 1931 menu_free(menu); 1932 if (flag != '\0') { 1933 xasprintf(&tmp, "-%c%s", flag, list[0]); 1934 free(list[0]); 1935 } else 1936 tmp = list[0]; 1937 free(list); 1938 free(spm); 1939 return (tmp); 1940 } 1941 if (height > size) 1942 height = size; 1943 1944 spm->size = size; 1945 spm->list = list; 1946 1947 if (options_get_number(c->session->options, "status-position") == 0) 1948 py = lines; 1949 else 1950 py = c->tty.sy - 3 - height; 1951 offset += utf8_cstrwidth(c->prompt_string); 1952 if (offset > 2) 1953 offset -= 2; 1954 else 1955 offset = 0; 1956 1957 if (menu_display(menu, MENU_NOMOUSE|MENU_TAB, 0, NULL, offset, py, c, 1958 BOX_LINES_DEFAULT, NULL, NULL, NULL, NULL, 1959 status_prompt_menu_callback, spm) != 0) { 1960 menu_free(menu); 1961 free(spm); 1962 return (NULL); 1963 } 1964 return (NULL); 1965 } 1966 1967 /* Sort complete list. */ 1968 static int 1969 status_prompt_complete_sort(const void *a, const void *b) 1970 { 1971 const char **aa = __UNCONST(a), **bb = __UNCONST(b); 1972 1973 return (strcmp(*aa, *bb)); 1974 } 1975 1976 /* Complete a session. */ 1977 static char * 1978 status_prompt_complete_session(char ***list, u_int *size, const char *s, 1979 char flag) 1980 { 1981 struct session *loop; 1982 char *out, *tmp, n[11]; 1983 1984 RB_FOREACH(loop, sessions, &sessions) { 1985 if (*s == '\0' || strncmp(loop->name, s, strlen(s)) == 0) { 1986 *list = xreallocarray(*list, (*size) + 2, 1987 sizeof **list); 1988 xasprintf(&(*list)[(*size)++], "%s:", loop->name); 1989 } else if (*s == '$') { 1990 xsnprintf(n, sizeof n, "%u", loop->id); 1991 if (s[1] == '\0' || 1992 strncmp(n, s + 1, strlen(s) - 1) == 0) { 1993 *list = xreallocarray(*list, (*size) + 2, 1994 sizeof **list); 1995 xasprintf(&(*list)[(*size)++], "$%s:", n); 1996 } 1997 } 1998 } 1999 out = status_prompt_complete_prefix(*list, *size); 2000 if (out != NULL && flag != '\0') { 2001 xasprintf(&tmp, "-%c%s", flag, out); 2002 free(out); 2003 out = tmp; 2004 } 2005 return (out); 2006 } 2007 2008 /* Complete word. */ 2009 static char * 2010 status_prompt_complete(struct client *c, const char *word, u_int offset) 2011 { 2012 struct session *session; 2013 const char *s, *colon; 2014 char **list = NULL, *copy = NULL, *out = NULL; 2015 char flag = '\0'; 2016 u_int size = 0, i; 2017 2018 if (*word == '\0' && 2019 c->prompt_type != PROMPT_TYPE_TARGET && 2020 c->prompt_type != PROMPT_TYPE_WINDOW_TARGET) 2021 return (NULL); 2022 2023 if (c->prompt_type != PROMPT_TYPE_TARGET && 2024 c->prompt_type != PROMPT_TYPE_WINDOW_TARGET && 2025 strncmp(word, "-t", 2) != 0 && 2026 strncmp(word, "-s", 2) != 0) { 2027 list = status_prompt_complete_list(&size, word, offset == 0); 2028 if (size == 0) 2029 out = NULL; 2030 else if (size == 1) 2031 xasprintf(&out, "%s ", list[0]); 2032 else 2033 out = status_prompt_complete_prefix(list, size); 2034 goto found; 2035 } 2036 2037 if (c->prompt_type == PROMPT_TYPE_TARGET || 2038 c->prompt_type == PROMPT_TYPE_WINDOW_TARGET) { 2039 s = word; 2040 flag = '\0'; 2041 } else { 2042 s = word + 2; 2043 flag = word[1]; 2044 offset += 2; 2045 } 2046 2047 /* If this is a window completion, open the window menu. */ 2048 if (c->prompt_type == PROMPT_TYPE_WINDOW_TARGET) { 2049 out = status_prompt_complete_window_menu(c, c->session, s, 2050 offset, '\0'); 2051 goto found; 2052 } 2053 colon = strchr(s, ':'); 2054 2055 /* If there is no colon, complete as a session. */ 2056 if (colon == NULL) { 2057 out = status_prompt_complete_session(&list, &size, s, flag); 2058 goto found; 2059 } 2060 2061 /* If there is a colon but no period, find session and show a menu. */ 2062 if (strchr(colon + 1, '.') == NULL) { 2063 if (*s == ':') 2064 session = c->session; 2065 else { 2066 copy = xstrdup(s); 2067 *strchr(copy, ':') = '\0'; 2068 session = session_find(copy); 2069 free(copy); 2070 if (session == NULL) 2071 goto found; 2072 } 2073 out = status_prompt_complete_window_menu(c, session, colon + 1, 2074 offset, flag); 2075 if (out == NULL) 2076 return (NULL); 2077 } 2078 2079 found: 2080 if (size != 0) { 2081 qsort(list, size, sizeof *list, status_prompt_complete_sort); 2082 for (i = 0; i < size; i++) 2083 log_debug("complete %u: %s", i, list[i]); 2084 } 2085 2086 if (out != NULL && strcmp(word, out) == 0) { 2087 free(out); 2088 out = NULL; 2089 } 2090 if (out != NULL || 2091 !status_prompt_complete_list_menu(c, list, size, offset, flag)) { 2092 for (i = 0; i < size; i++) 2093 free(list[i]); 2094 free(list); 2095 } 2096 return (out); 2097 } 2098 2099 /* Return the type of the prompt as an enum. */ 2100 enum prompt_type 2101 status_prompt_type(const char *type) 2102 { 2103 u_int i; 2104 2105 for (i = 0; i < PROMPT_NTYPES; i++) { 2106 if (strcmp(type, status_prompt_type_string(i)) == 0) 2107 return (i); 2108 } 2109 return (PROMPT_TYPE_INVALID); 2110 } 2111 2112 /* Accessor for prompt_type_strings. */ 2113 const char * 2114 status_prompt_type_string(u_int type) 2115 { 2116 if (type >= PROMPT_NTYPES) 2117 return ("invalid"); 2118 return (prompt_type_strings[type]); 2119 } 2120