1 /* $OpenBSD: status.c,v 1.197 2019/05/03 10:00:48 nicm Exp $ */ 2 3 /* 4 * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 15 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 16 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <sys/types.h> 20 #include <sys/time.h> 21 22 #include <errno.h> 23 #include <limits.h> 24 #include <stdarg.h> 25 #include <stdlib.h> 26 #include <string.h> 27 #include <time.h> 28 #include <unistd.h> 29 30 #include "tmux.h" 31 32 static void status_message_callback(int, short, void *); 33 static void status_timer_callback(int, short, void *); 34 35 static char *status_prompt_find_history_file(void); 36 static const char *status_prompt_up_history(u_int *); 37 static const char *status_prompt_down_history(u_int *); 38 static void status_prompt_add_history(const char *); 39 40 static char **status_prompt_complete_list(u_int *, const char *); 41 static char *status_prompt_complete_prefix(char **, u_int); 42 static char *status_prompt_complete(struct session *, const char *); 43 44 /* Status prompt history. */ 45 #define PROMPT_HISTORY 100 46 static char **status_prompt_hlist; 47 static u_int status_prompt_hsize; 48 49 /* Find the history file to load/save from/to. */ 50 static char * 51 status_prompt_find_history_file(void) 52 { 53 const char *home, *history_file; 54 char *path; 55 56 history_file = options_get_string(global_options, "history-file"); 57 if (*history_file == '\0') 58 return (NULL); 59 if (*history_file == '/') 60 return (xstrdup(history_file)); 61 62 if (history_file[0] != '~' || history_file[1] != '/') 63 return (NULL); 64 if ((home = find_home()) == NULL) 65 return (NULL); 66 xasprintf(&path, "%s%s", home, history_file + 1); 67 return (path); 68 } 69 70 /* Load status prompt history from file. */ 71 void 72 status_prompt_load_history(void) 73 { 74 FILE *f; 75 char *history_file, *line, *tmp; 76 size_t length; 77 78 if ((history_file = status_prompt_find_history_file()) == NULL) 79 return; 80 log_debug("loading history from %s", history_file); 81 82 f = fopen(history_file, "r"); 83 if (f == NULL) { 84 log_debug("%s: %s", history_file, strerror(errno)); 85 free(history_file); 86 return; 87 } 88 free(history_file); 89 90 for (;;) { 91 if ((line = fgetln(f, &length)) == NULL) 92 break; 93 94 if (length > 0) { 95 if (line[length - 1] == '\n') { 96 line[length - 1] = '\0'; 97 status_prompt_add_history(line); 98 } else { 99 tmp = xmalloc(length + 1); 100 memcpy(tmp, line, length); 101 tmp[length] = '\0'; 102 status_prompt_add_history(tmp); 103 free(tmp); 104 } 105 } 106 } 107 fclose(f); 108 } 109 110 /* Save status prompt history to file. */ 111 void 112 status_prompt_save_history(void) 113 { 114 FILE *f; 115 u_int i; 116 char *history_file; 117 118 if ((history_file = status_prompt_find_history_file()) == NULL) 119 return; 120 log_debug("saving history to %s", history_file); 121 122 f = fopen(history_file, "w"); 123 if (f == NULL) { 124 log_debug("%s: %s", history_file, strerror(errno)); 125 free(history_file); 126 return; 127 } 128 free(history_file); 129 130 for (i = 0; i < status_prompt_hsize; i++) { 131 fputs(status_prompt_hlist[i], f); 132 fputc('\n', f); 133 } 134 fclose(f); 135 136 } 137 138 /* Status timer callback. */ 139 static void 140 status_timer_callback(__unused int fd, __unused short events, void *arg) 141 { 142 struct client *c = arg; 143 struct session *s = c->session; 144 struct timeval tv; 145 146 evtimer_del(&c->status.timer); 147 148 if (s == NULL) 149 return; 150 151 if (c->message_string == NULL && c->prompt_string == NULL) 152 c->flags |= CLIENT_REDRAWSTATUS; 153 154 timerclear(&tv); 155 tv.tv_sec = options_get_number(s->options, "status-interval"); 156 157 if (tv.tv_sec != 0) 158 evtimer_add(&c->status.timer, &tv); 159 log_debug("client %p, status interval %d", c, (int)tv.tv_sec); 160 } 161 162 /* Start status timer for client. */ 163 void 164 status_timer_start(struct client *c) 165 { 166 struct session *s = c->session; 167 168 if (event_initialized(&c->status.timer)) 169 evtimer_del(&c->status.timer); 170 else 171 evtimer_set(&c->status.timer, status_timer_callback, c); 172 173 if (s != NULL && options_get_number(s->options, "status")) 174 status_timer_callback(-1, 0, c); 175 } 176 177 /* Start status timer for all clients. */ 178 void 179 status_timer_start_all(void) 180 { 181 struct client *c; 182 183 TAILQ_FOREACH(c, &clients, entry) 184 status_timer_start(c); 185 } 186 187 /* Update status cache. */ 188 void 189 status_update_cache(struct session *s) 190 { 191 s->statuslines = options_get_number(s->options, "status"); 192 if (s->statuslines == 0) 193 s->statusat = -1; 194 else if (options_get_number(s->options, "status-position") == 0) 195 s->statusat = 0; 196 else 197 s->statusat = 1; 198 } 199 200 /* Get screen line of status line. -1 means off. */ 201 int 202 status_at_line(struct client *c) 203 { 204 struct session *s = c->session; 205 206 if (c->flags & CLIENT_STATUSOFF) 207 return (-1); 208 if (s->statusat != 1) 209 return (s->statusat); 210 return (c->tty.sy - status_line_size(c)); 211 } 212 213 /* Get size of status line for client's session. 0 means off. */ 214 u_int 215 status_line_size(struct client *c) 216 { 217 struct session *s = c->session; 218 219 if (c->flags & CLIENT_STATUSOFF) 220 return (0); 221 return (s->statuslines); 222 } 223 224 /* Get window at window list position. */ 225 struct style_range * 226 status_get_range(struct client *c, u_int x, u_int y) 227 { 228 struct status_line *sl = &c->status; 229 struct style_range *sr; 230 231 if (y >= nitems(sl->entries)) 232 return (NULL); 233 TAILQ_FOREACH(sr, &sl->entries[y].ranges, entry) { 234 if (x >= sr->start && x < sr->end) 235 return (sr); 236 } 237 return (NULL); 238 } 239 240 /* Free all ranges. */ 241 static void 242 status_free_ranges(struct style_ranges *srs) 243 { 244 struct style_range *sr, *sr1; 245 246 TAILQ_FOREACH_SAFE(sr, srs, entry, sr1) { 247 TAILQ_REMOVE(srs, sr, entry); 248 free(sr); 249 } 250 } 251 252 /* Save old status line. */ 253 static void 254 status_push_screen(struct client *c) 255 { 256 struct status_line *sl = &c->status; 257 258 if (sl->active == &sl->screen) { 259 sl->active = xmalloc(sizeof *sl->active); 260 screen_init(sl->active, c->tty.sx, status_line_size(c), 0); 261 } 262 sl->references++; 263 } 264 265 /* Restore old status line. */ 266 static void 267 status_pop_screen(struct client *c) 268 { 269 struct status_line *sl = &c->status; 270 271 if (--sl->references == 0) { 272 screen_free(sl->active); 273 free(sl->active); 274 sl->active = &sl->screen; 275 } 276 } 277 278 /* Initialize status line. */ 279 void 280 status_init(struct client *c) 281 { 282 struct status_line *sl = &c->status; 283 u_int i; 284 285 for (i = 0; i < nitems(sl->entries); i++) 286 TAILQ_INIT(&sl->entries[i].ranges); 287 288 screen_init(&sl->screen, c->tty.sx, 1, 0); 289 sl->active = &sl->screen; 290 } 291 292 /* Free status line. */ 293 void 294 status_free(struct client *c) 295 { 296 struct status_line *sl = &c->status; 297 u_int i; 298 299 for (i = 0; i < nitems(sl->entries); i++) { 300 status_free_ranges(&sl->entries[i].ranges); 301 free((void *)sl->entries[i].expanded); 302 } 303 304 if (event_initialized(&sl->timer)) 305 evtimer_del(&sl->timer); 306 307 if (sl->active != &sl->screen) { 308 screen_free(sl->active); 309 free(sl->active); 310 } 311 screen_free(&sl->screen); 312 } 313 314 /* Draw status line for client. */ 315 int 316 status_redraw(struct client *c) 317 { 318 struct status_line *sl = &c->status; 319 struct status_line_entry *sle; 320 struct session *s = c->session; 321 struct screen_write_ctx ctx; 322 struct grid_cell gc; 323 u_int lines, i, n, width = c->tty.sx; 324 int flags, force = 0, changed = 0; 325 struct options_entry *o; 326 union options_value *ov; 327 struct format_tree *ft; 328 char *expanded; 329 330 log_debug("%s enter", __func__); 331 332 /* Shouldn't get here if not the active screen. */ 333 if (sl->active != &sl->screen) 334 fatalx("not the active screen"); 335 336 /* No status line? */ 337 lines = status_line_size(c); 338 if (c->tty.sy == 0 || lines == 0) 339 return (1); 340 341 /* Set up default colour. */ 342 style_apply(&gc, s->options, "status-style"); 343 if (!grid_cells_equal(&gc, &sl->style)) { 344 force = 1; 345 memcpy(&sl->style, &gc, sizeof sl->style); 346 } 347 348 /* Resize the target screen. */ 349 if (screen_size_x(&sl->screen) != width || 350 screen_size_y(&sl->screen) != lines) { 351 if (screen_size_x(&sl->screen) != width) 352 force = 1; 353 screen_resize(&sl->screen, width, lines, 0); 354 changed = 1; 355 } 356 screen_write_start(&ctx, NULL, &sl->screen); 357 358 /* Create format tree. */ 359 flags = FORMAT_STATUS; 360 if (c->flags & CLIENT_STATUSFORCE) 361 flags |= FORMAT_FORCE; 362 ft = format_create(c, NULL, FORMAT_NONE, flags); 363 format_defaults(ft, c, NULL, NULL, NULL); 364 365 /* Write the status lines. */ 366 o = options_get(s->options, "status-format"); 367 if (o == NULL) { 368 for (n = 0; n < width * lines; n++) 369 screen_write_putc(&ctx, &gc, ' '); 370 } else { 371 for (i = 0; i < lines; i++) { 372 screen_write_cursormove(&ctx, 0, i, 0); 373 374 ov = options_array_get(o, i); 375 if (ov == NULL) { 376 for (n = 0; n < width; n++) 377 screen_write_putc(&ctx, &gc, ' '); 378 continue; 379 } 380 sle = &sl->entries[i]; 381 382 expanded = format_expand_time(ft, ov->string); 383 if (!force && 384 sle->expanded != NULL && 385 strcmp(expanded, sle->expanded) == 0) { 386 free(expanded); 387 continue; 388 } 389 changed = 1; 390 391 for (n = 0; n < width; n++) 392 screen_write_putc(&ctx, &gc, ' '); 393 screen_write_cursormove(&ctx, 0, i, 0); 394 395 status_free_ranges(&sle->ranges); 396 format_draw(&ctx, &gc, width, expanded, &sle->ranges); 397 398 free(sle->expanded); 399 sle->expanded = expanded; 400 } 401 } 402 screen_write_stop(&ctx); 403 404 /* Free the format tree. */ 405 format_free(ft); 406 407 /* Return if the status line has changed. */ 408 log_debug("%s exit: force=%d, changed=%d", __func__, force, changed); 409 return (force || changed); 410 } 411 412 /* Set a status line message. */ 413 void 414 status_message_set(struct client *c, const char *fmt, ...) 415 { 416 struct timeval tv; 417 va_list ap; 418 int delay; 419 420 status_message_clear(c); 421 status_push_screen(c); 422 423 va_start(ap, fmt); 424 xvasprintf(&c->message_string, fmt, ap); 425 va_end(ap); 426 427 server_client_add_message(c, "%s", c->message_string); 428 429 delay = options_get_number(c->session->options, "display-time"); 430 if (delay > 0) { 431 tv.tv_sec = delay / 1000; 432 tv.tv_usec = (delay % 1000) * 1000L; 433 434 if (event_initialized(&c->message_timer)) 435 evtimer_del(&c->message_timer); 436 evtimer_set(&c->message_timer, status_message_callback, c); 437 evtimer_add(&c->message_timer, &tv); 438 } 439 440 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE); 441 c->flags |= CLIENT_REDRAWSTATUS; 442 } 443 444 /* Clear status line message. */ 445 void 446 status_message_clear(struct client *c) 447 { 448 if (c->message_string == NULL) 449 return; 450 451 free(c->message_string); 452 c->message_string = NULL; 453 454 if (c->prompt_string == NULL) 455 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE); 456 c->flags |= CLIENT_ALLREDRAWFLAGS; /* was frozen and may have changed */ 457 458 status_pop_screen(c); 459 } 460 461 /* Clear status line message after timer expires. */ 462 static void 463 status_message_callback(__unused int fd, __unused short event, void *data) 464 { 465 struct client *c = data; 466 467 status_message_clear(c); 468 } 469 470 /* Draw client message on status line of present else on last line. */ 471 int 472 status_message_redraw(struct client *c) 473 { 474 struct status_line *sl = &c->status; 475 struct screen_write_ctx ctx; 476 struct session *s = c->session; 477 struct screen old_screen; 478 size_t len; 479 u_int lines, offset; 480 struct grid_cell gc; 481 482 if (c->tty.sx == 0 || c->tty.sy == 0) 483 return (0); 484 memcpy(&old_screen, sl->active, sizeof old_screen); 485 486 lines = status_line_size(c); 487 if (lines <= 1) 488 lines = 1; 489 screen_init(sl->active, c->tty.sx, lines, 0); 490 491 len = screen_write_strlen("%s", c->message_string); 492 if (len > c->tty.sx) 493 len = c->tty.sx; 494 495 style_apply(&gc, s->options, "message-style"); 496 497 screen_write_start(&ctx, NULL, sl->active); 498 screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines - 1); 499 screen_write_cursormove(&ctx, 0, lines - 1, 0); 500 for (offset = 0; offset < c->tty.sx; offset++) 501 screen_write_putc(&ctx, &gc, ' '); 502 screen_write_cursormove(&ctx, 0, lines - 1, 0); 503 screen_write_nputs(&ctx, len, &gc, "%s", c->message_string); 504 screen_write_stop(&ctx); 505 506 if (grid_compare(sl->active->grid, old_screen.grid) == 0) { 507 screen_free(&old_screen); 508 return (0); 509 } 510 screen_free(&old_screen); 511 return (1); 512 } 513 514 /* Enable status line prompt. */ 515 void 516 status_prompt_set(struct client *c, const char *msg, const char *input, 517 prompt_input_cb inputcb, prompt_free_cb freecb, void *data, int flags) 518 { 519 struct format_tree *ft; 520 char *tmp, *cp; 521 522 ft = format_create(c, NULL, FORMAT_NONE, 0); 523 format_defaults(ft, c, NULL, NULL, NULL); 524 525 if (input == NULL) 526 input = ""; 527 if (flags & PROMPT_NOFORMAT) 528 tmp = xstrdup(input); 529 else 530 tmp = format_expand_time(ft, input); 531 532 status_message_clear(c); 533 status_prompt_clear(c); 534 status_push_screen(c); 535 536 c->prompt_string = format_expand_time(ft, msg); 537 538 c->prompt_buffer = utf8_fromcstr(tmp); 539 c->prompt_index = utf8_strlen(c->prompt_buffer); 540 541 c->prompt_inputcb = inputcb; 542 c->prompt_freecb = freecb; 543 c->prompt_data = data; 544 545 c->prompt_hindex = 0; 546 547 c->prompt_flags = flags; 548 c->prompt_mode = PROMPT_ENTRY; 549 550 if (~flags & PROMPT_INCREMENTAL) 551 c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE); 552 c->flags |= CLIENT_REDRAWSTATUS; 553 554 if ((flags & PROMPT_INCREMENTAL) && *tmp != '\0') { 555 xasprintf(&cp, "=%s", tmp); 556 c->prompt_inputcb(c, c->prompt_data, cp, 0); 557 free(cp); 558 } 559 560 free(tmp); 561 format_free(ft); 562 } 563 564 /* Remove status line prompt. */ 565 void 566 status_prompt_clear(struct client *c) 567 { 568 if (c->prompt_string == NULL) 569 return; 570 571 if (c->prompt_freecb != NULL && c->prompt_data != NULL) 572 c->prompt_freecb(c->prompt_data); 573 574 free(c->prompt_string); 575 c->prompt_string = NULL; 576 577 free(c->prompt_buffer); 578 c->prompt_buffer = NULL; 579 580 free(c->prompt_saved); 581 c->prompt_saved = NULL; 582 583 c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE); 584 c->flags |= CLIENT_ALLREDRAWFLAGS; /* was frozen and may have changed */ 585 586 status_pop_screen(c); 587 } 588 589 /* Update status line prompt with a new prompt string. */ 590 void 591 status_prompt_update(struct client *c, const char *msg, const char *input) 592 { 593 struct format_tree *ft; 594 char *tmp; 595 596 ft = format_create(c, NULL, FORMAT_NONE, 0); 597 format_defaults(ft, c, NULL, NULL, NULL); 598 599 tmp = format_expand_time(ft, input); 600 601 free(c->prompt_string); 602 c->prompt_string = format_expand_time(ft, msg); 603 604 free(c->prompt_buffer); 605 c->prompt_buffer = utf8_fromcstr(tmp); 606 c->prompt_index = utf8_strlen(c->prompt_buffer); 607 608 c->prompt_hindex = 0; 609 610 c->flags |= CLIENT_REDRAWSTATUS; 611 612 free(tmp); 613 format_free(ft); 614 } 615 616 /* Draw client prompt on status line of present else on last line. */ 617 int 618 status_prompt_redraw(struct client *c) 619 { 620 struct status_line *sl = &c->status; 621 struct screen_write_ctx ctx; 622 struct session *s = c->session; 623 struct screen old_screen; 624 u_int i, lines, offset, left, start, width; 625 u_int pcursor, pwidth; 626 struct grid_cell gc, cursorgc; 627 628 if (c->tty.sx == 0 || c->tty.sy == 0) 629 return (0); 630 memcpy(&old_screen, sl->active, sizeof old_screen); 631 632 lines = status_line_size(c); 633 if (lines <= 1) 634 lines = 1; 635 screen_init(sl->active, c->tty.sx, lines, 0); 636 637 if (c->prompt_mode == PROMPT_COMMAND) 638 style_apply(&gc, s->options, "message-command-style"); 639 else 640 style_apply(&gc, s->options, "message-style"); 641 642 memcpy(&cursorgc, &gc, sizeof cursorgc); 643 cursorgc.attr ^= GRID_ATTR_REVERSE; 644 645 start = screen_write_strlen("%s", c->prompt_string); 646 if (start > c->tty.sx) 647 start = c->tty.sx; 648 649 screen_write_start(&ctx, NULL, sl->active); 650 screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines - 1); 651 screen_write_cursormove(&ctx, 0, lines - 1, 0); 652 for (offset = 0; offset < c->tty.sx; offset++) 653 screen_write_putc(&ctx, &gc, ' '); 654 screen_write_cursormove(&ctx, 0, lines - 1, 0); 655 screen_write_nputs(&ctx, start, &gc, "%s", c->prompt_string); 656 screen_write_cursormove(&ctx, start, lines - 1, 0); 657 658 left = c->tty.sx - start; 659 if (left == 0) 660 goto finished; 661 662 pcursor = utf8_strwidth(c->prompt_buffer, c->prompt_index); 663 pwidth = utf8_strwidth(c->prompt_buffer, -1); 664 if (pcursor >= left) { 665 /* 666 * The cursor would be outside the screen so start drawing 667 * with it on the right. 668 */ 669 offset = (pcursor - left) + 1; 670 pwidth = left; 671 } else 672 offset = 0; 673 if (pwidth > left) 674 pwidth = left; 675 676 width = 0; 677 for (i = 0; c->prompt_buffer[i].size != 0; i++) { 678 if (width < offset) { 679 width += c->prompt_buffer[i].width; 680 continue; 681 } 682 if (width >= offset + pwidth) 683 break; 684 width += c->prompt_buffer[i].width; 685 if (width > offset + pwidth) 686 break; 687 688 if (i != c->prompt_index) { 689 utf8_copy(&gc.data, &c->prompt_buffer[i]); 690 screen_write_cell(&ctx, &gc); 691 } else { 692 utf8_copy(&cursorgc.data, &c->prompt_buffer[i]); 693 screen_write_cell(&ctx, &cursorgc); 694 } 695 } 696 if (sl->active->cx < screen_size_x(sl->active) && c->prompt_index >= i) 697 screen_write_putc(&ctx, &cursorgc, ' '); 698 699 finished: 700 screen_write_stop(&ctx); 701 702 if (grid_compare(sl->active->grid, old_screen.grid) == 0) { 703 screen_free(&old_screen); 704 return (0); 705 } 706 screen_free(&old_screen); 707 return (1); 708 } 709 710 /* Is this a separator? */ 711 static int 712 status_prompt_in_list(const char *ws, const struct utf8_data *ud) 713 { 714 if (ud->size != 1 || ud->width != 1) 715 return (0); 716 return (strchr(ws, *ud->data) != NULL); 717 } 718 719 /* Is this a space? */ 720 static int 721 status_prompt_space(const struct utf8_data *ud) 722 { 723 if (ud->size != 1 || ud->width != 1) 724 return (0); 725 return (*ud->data == ' '); 726 } 727 728 /* 729 * Translate key from emacs to vi. Return 0 to drop key, 1 to process the key 730 * as an emacs key; return 2 to append to the buffer. 731 */ 732 static int 733 status_prompt_translate_key(struct client *c, key_code key, key_code *new_key) 734 { 735 if (c->prompt_mode == PROMPT_ENTRY) { 736 switch (key) { 737 case '\003': /* C-c */ 738 case '\010': /* C-h */ 739 case '\011': /* Tab */ 740 case '\025': /* C-u */ 741 case '\027': /* C-w */ 742 case '\n': 743 case '\r': 744 case KEYC_BSPACE: 745 case KEYC_DC: 746 case KEYC_DOWN: 747 case KEYC_END: 748 case KEYC_HOME: 749 case KEYC_LEFT: 750 case KEYC_RIGHT: 751 case KEYC_UP: 752 *new_key = key; 753 return (1); 754 case '\033': /* Escape */ 755 c->prompt_mode = PROMPT_COMMAND; 756 c->flags |= CLIENT_REDRAWSTATUS; 757 return (0); 758 } 759 *new_key = key; 760 return (2); 761 } 762 763 switch (key) { 764 case 'A': 765 case 'I': 766 case 'C': 767 case 's': 768 case 'a': 769 c->prompt_mode = PROMPT_ENTRY; 770 c->flags |= CLIENT_REDRAWSTATUS; 771 break; /* switch mode and... */ 772 case 'S': 773 c->prompt_mode = PROMPT_ENTRY; 774 c->flags |= CLIENT_REDRAWSTATUS; 775 *new_key = '\025'; /* C-u */ 776 return (1); 777 case 'i': 778 case '\033': /* Escape */ 779 c->prompt_mode = PROMPT_ENTRY; 780 c->flags |= CLIENT_REDRAWSTATUS; 781 return (0); 782 } 783 784 switch (key) { 785 case 'A': 786 case '$': 787 *new_key = KEYC_END; 788 return (1); 789 case 'I': 790 case '0': 791 case '^': 792 *new_key = KEYC_HOME; 793 return (1); 794 case 'C': 795 case 'D': 796 *new_key = '\013'; /* C-k */ 797 return (1); 798 case KEYC_BSPACE: 799 case 'X': 800 *new_key = KEYC_BSPACE; 801 return (1); 802 case 'b': 803 case 'B': 804 *new_key = 'b'|KEYC_ESCAPE; 805 return (1); 806 case 'd': 807 *new_key = '\025'; 808 return (1); 809 case 'e': 810 case 'E': 811 case 'w': 812 case 'W': 813 *new_key = 'f'|KEYC_ESCAPE; 814 return (1); 815 case 'p': 816 *new_key = '\031'; /* C-y */ 817 return (1); 818 case 's': 819 case KEYC_DC: 820 case 'x': 821 *new_key = KEYC_DC; 822 return (1); 823 case KEYC_DOWN: 824 case 'j': 825 *new_key = KEYC_DOWN; 826 return (1); 827 case KEYC_LEFT: 828 case 'h': 829 *new_key = KEYC_LEFT; 830 return (1); 831 case 'a': 832 case KEYC_RIGHT: 833 case 'l': 834 *new_key = KEYC_RIGHT; 835 return (1); 836 case KEYC_UP: 837 case 'k': 838 *new_key = KEYC_UP; 839 return (1); 840 case '\010' /* C-h */: 841 case '\003' /* C-c */: 842 case '\n': 843 case '\r': 844 return (1); 845 } 846 return (0); 847 } 848 849 /* Handle keys in prompt. */ 850 int 851 status_prompt_key(struct client *c, key_code key) 852 { 853 struct options *oo = c->session->options; 854 struct paste_buffer *pb; 855 char *s, *cp, word[64], prefix = '='; 856 const char *histstr, *bufdata, *ws = NULL; 857 u_char ch; 858 size_t size, n, off, idx, bufsize, used; 859 struct utf8_data tmp, *first, *last, *ud; 860 int keys; 861 862 size = utf8_strlen(c->prompt_buffer); 863 864 if (c->prompt_flags & PROMPT_NUMERIC) { 865 if (key >= '0' && key <= '9') 866 goto append_key; 867 s = utf8_tocstr(c->prompt_buffer); 868 c->prompt_inputcb(c, c->prompt_data, s, 1); 869 status_prompt_clear(c); 870 free(s); 871 return (1); 872 } 873 key &= ~KEYC_XTERM; 874 875 keys = options_get_number(c->session->options, "status-keys"); 876 if (keys == MODEKEY_VI) { 877 switch (status_prompt_translate_key(c, key, &key)) { 878 case 1: 879 goto process_key; 880 case 2: 881 goto append_key; 882 default: 883 return (0); 884 } 885 } 886 887 process_key: 888 switch (key) { 889 case KEYC_LEFT: 890 case '\002': /* C-b */ 891 if (c->prompt_index > 0) { 892 c->prompt_index--; 893 break; 894 } 895 break; 896 case KEYC_RIGHT: 897 case '\006': /* C-f */ 898 if (c->prompt_index < size) { 899 c->prompt_index++; 900 break; 901 } 902 break; 903 case KEYC_HOME: 904 case '\001': /* C-a */ 905 if (c->prompt_index != 0) { 906 c->prompt_index = 0; 907 break; 908 } 909 break; 910 case KEYC_END: 911 case '\005': /* C-e */ 912 if (c->prompt_index != size) { 913 c->prompt_index = size; 914 break; 915 } 916 break; 917 case '\011': /* Tab */ 918 if (c->prompt_buffer[0].size == 0) 919 break; 920 921 idx = c->prompt_index; 922 if (idx != 0) 923 idx--; 924 925 /* Find the word we are in. */ 926 first = &c->prompt_buffer[idx]; 927 while (first > c->prompt_buffer && !status_prompt_space(first)) 928 first--; 929 while (first->size != 0 && status_prompt_space(first)) 930 first++; 931 last = &c->prompt_buffer[idx]; 932 while (last->size != 0 && !status_prompt_space(last)) 933 last++; 934 while (last > c->prompt_buffer && status_prompt_space(last)) 935 last--; 936 if (last->size != 0) 937 last++; 938 if (last <= first) 939 break; 940 941 used = 0; 942 for (ud = first; ud < last; ud++) { 943 if (used + ud->size >= sizeof word) 944 break; 945 memcpy(word + used, ud->data, ud->size); 946 used += ud->size; 947 } 948 if (ud != last) 949 break; 950 word[used] = '\0'; 951 952 /* And try to complete it. */ 953 if ((s = status_prompt_complete(c->session, word)) == NULL) 954 break; 955 956 /* Trim out word. */ 957 n = size - (last - c->prompt_buffer) + 1; /* with \0 */ 958 memmove(first, last, n * sizeof *c->prompt_buffer); 959 size -= last - first; 960 961 /* Insert the new word. */ 962 size += strlen(s); 963 off = first - c->prompt_buffer; 964 c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 1, 965 sizeof *c->prompt_buffer); 966 first = c->prompt_buffer + off; 967 memmove(first + strlen(s), first, n * sizeof *c->prompt_buffer); 968 for (idx = 0; idx < strlen(s); idx++) 969 utf8_set(&first[idx], s[idx]); 970 971 c->prompt_index = (first - c->prompt_buffer) + strlen(s); 972 free(s); 973 974 goto changed; 975 case KEYC_BSPACE: 976 case '\010': /* C-h */ 977 if (c->prompt_index != 0) { 978 if (c->prompt_index == size) 979 c->prompt_buffer[--c->prompt_index].size = 0; 980 else { 981 memmove(c->prompt_buffer + c->prompt_index - 1, 982 c->prompt_buffer + c->prompt_index, 983 (size + 1 - c->prompt_index) * 984 sizeof *c->prompt_buffer); 985 c->prompt_index--; 986 } 987 goto changed; 988 } 989 break; 990 case KEYC_DC: 991 case '\004': /* C-d */ 992 if (c->prompt_index != size) { 993 memmove(c->prompt_buffer + c->prompt_index, 994 c->prompt_buffer + c->prompt_index + 1, 995 (size + 1 - c->prompt_index) * 996 sizeof *c->prompt_buffer); 997 goto changed; 998 } 999 break; 1000 case '\025': /* C-u */ 1001 c->prompt_buffer[0].size = 0; 1002 c->prompt_index = 0; 1003 goto changed; 1004 case '\013': /* C-k */ 1005 if (c->prompt_index < size) { 1006 c->prompt_buffer[c->prompt_index].size = 0; 1007 goto changed; 1008 } 1009 break; 1010 case '\027': /* C-w */ 1011 ws = options_get_string(oo, "word-separators"); 1012 idx = c->prompt_index; 1013 1014 /* Find a non-separator. */ 1015 while (idx != 0) { 1016 idx--; 1017 if (!status_prompt_in_list(ws, &c->prompt_buffer[idx])) 1018 break; 1019 } 1020 1021 /* Find the separator at the beginning of the word. */ 1022 while (idx != 0) { 1023 idx--; 1024 if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) { 1025 /* Go back to the word. */ 1026 idx++; 1027 break; 1028 } 1029 } 1030 1031 free(c->prompt_saved); 1032 c->prompt_saved = xcalloc(sizeof *c->prompt_buffer, 1033 (c->prompt_index - idx) + 1); 1034 memcpy(c->prompt_saved, c->prompt_buffer + idx, 1035 (c->prompt_index - idx) * sizeof *c->prompt_buffer); 1036 1037 memmove(c->prompt_buffer + idx, 1038 c->prompt_buffer + c->prompt_index, 1039 (size + 1 - c->prompt_index) * 1040 sizeof *c->prompt_buffer); 1041 memset(c->prompt_buffer + size - (c->prompt_index - idx), 1042 '\0', (c->prompt_index - idx) * sizeof *c->prompt_buffer); 1043 c->prompt_index = idx; 1044 1045 goto changed; 1046 case 'f'|KEYC_ESCAPE: 1047 case KEYC_RIGHT|KEYC_CTRL: 1048 ws = options_get_string(oo, "word-separators"); 1049 1050 /* Find a word. */ 1051 while (c->prompt_index != size) { 1052 idx = ++c->prompt_index; 1053 if (!status_prompt_in_list(ws, &c->prompt_buffer[idx])) 1054 break; 1055 } 1056 1057 /* Find the separator at the end of the word. */ 1058 while (c->prompt_index != size) { 1059 idx = ++c->prompt_index; 1060 if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) 1061 break; 1062 } 1063 1064 /* Back up to the end-of-word like vi. */ 1065 if (options_get_number(oo, "status-keys") == MODEKEY_VI && 1066 c->prompt_index != 0) 1067 c->prompt_index--; 1068 1069 goto changed; 1070 case 'b'|KEYC_ESCAPE: 1071 case KEYC_LEFT|KEYC_CTRL: 1072 ws = options_get_string(oo, "word-separators"); 1073 1074 /* Find a non-separator. */ 1075 while (c->prompt_index != 0) { 1076 idx = --c->prompt_index; 1077 if (!status_prompt_in_list(ws, &c->prompt_buffer[idx])) 1078 break; 1079 } 1080 1081 /* Find the separator at the beginning of the word. */ 1082 while (c->prompt_index != 0) { 1083 idx = --c->prompt_index; 1084 if (status_prompt_in_list(ws, &c->prompt_buffer[idx])) { 1085 /* Go back to the word. */ 1086 c->prompt_index++; 1087 break; 1088 } 1089 } 1090 goto changed; 1091 case KEYC_UP: 1092 case '\020': /* C-p */ 1093 histstr = status_prompt_up_history(&c->prompt_hindex); 1094 if (histstr == NULL) 1095 break; 1096 free(c->prompt_buffer); 1097 c->prompt_buffer = utf8_fromcstr(histstr); 1098 c->prompt_index = utf8_strlen(c->prompt_buffer); 1099 goto changed; 1100 case KEYC_DOWN: 1101 case '\016': /* C-n */ 1102 histstr = status_prompt_down_history(&c->prompt_hindex); 1103 if (histstr == NULL) 1104 break; 1105 free(c->prompt_buffer); 1106 c->prompt_buffer = utf8_fromcstr(histstr); 1107 c->prompt_index = utf8_strlen(c->prompt_buffer); 1108 goto changed; 1109 case '\031': /* C-y */ 1110 if (c->prompt_saved != NULL) { 1111 ud = c->prompt_saved; 1112 n = utf8_strlen(c->prompt_saved); 1113 } else { 1114 if ((pb = paste_get_top(NULL)) == NULL) 1115 break; 1116 bufdata = paste_buffer_data(pb, &bufsize); 1117 for (n = 0; n < bufsize; n++) { 1118 ch = (u_char)bufdata[n]; 1119 if (ch < 32 || ch >= 127) 1120 break; 1121 } 1122 ud = xreallocarray(NULL, n, sizeof *ud); 1123 for (idx = 0; idx < n; idx++) 1124 utf8_set(&ud[idx], bufdata[idx]); 1125 } 1126 1127 c->prompt_buffer = xreallocarray(c->prompt_buffer, size + n + 1, 1128 sizeof *c->prompt_buffer); 1129 if (c->prompt_index == size) { 1130 memcpy(c->prompt_buffer + c->prompt_index, ud, 1131 n * sizeof *c->prompt_buffer); 1132 c->prompt_index += n; 1133 c->prompt_buffer[c->prompt_index].size = 0; 1134 } else { 1135 memmove(c->prompt_buffer + c->prompt_index + n, 1136 c->prompt_buffer + c->prompt_index, 1137 (size + 1 - c->prompt_index) * 1138 sizeof *c->prompt_buffer); 1139 memcpy(c->prompt_buffer + c->prompt_index, ud, 1140 n * sizeof *c->prompt_buffer); 1141 c->prompt_index += n; 1142 } 1143 1144 if (ud != c->prompt_saved) 1145 free(ud); 1146 goto changed; 1147 case '\024': /* C-t */ 1148 idx = c->prompt_index; 1149 if (idx < size) 1150 idx++; 1151 if (idx >= 2) { 1152 utf8_copy(&tmp, &c->prompt_buffer[idx - 2]); 1153 utf8_copy(&c->prompt_buffer[idx - 2], 1154 &c->prompt_buffer[idx - 1]); 1155 utf8_copy(&c->prompt_buffer[idx - 1], &tmp); 1156 c->prompt_index = idx; 1157 goto changed; 1158 } 1159 break; 1160 case '\r': 1161 case '\n': 1162 s = utf8_tocstr(c->prompt_buffer); 1163 if (*s != '\0') 1164 status_prompt_add_history(s); 1165 if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0) 1166 status_prompt_clear(c); 1167 free(s); 1168 break; 1169 case '\033': /* Escape */ 1170 case '\003': /* C-c */ 1171 case '\007': /* C-g */ 1172 if (c->prompt_inputcb(c, c->prompt_data, NULL, 1) == 0) 1173 status_prompt_clear(c); 1174 break; 1175 case '\022': /* C-r */ 1176 if (c->prompt_flags & PROMPT_INCREMENTAL) { 1177 prefix = '-'; 1178 goto changed; 1179 } 1180 break; 1181 case '\023': /* C-s */ 1182 if (c->prompt_flags & PROMPT_INCREMENTAL) { 1183 prefix = '+'; 1184 goto changed; 1185 } 1186 break; 1187 default: 1188 goto append_key; 1189 } 1190 1191 c->flags |= CLIENT_REDRAWSTATUS; 1192 return (0); 1193 1194 append_key: 1195 if (key <= 0x1f || key >= KEYC_BASE) 1196 return (0); 1197 if (utf8_split(key, &tmp) != UTF8_DONE) 1198 return (0); 1199 1200 c->prompt_buffer = xreallocarray(c->prompt_buffer, size + 2, 1201 sizeof *c->prompt_buffer); 1202 1203 if (c->prompt_index == size) { 1204 utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp); 1205 c->prompt_index++; 1206 c->prompt_buffer[c->prompt_index].size = 0; 1207 } else { 1208 memmove(c->prompt_buffer + c->prompt_index + 1, 1209 c->prompt_buffer + c->prompt_index, 1210 (size + 1 - c->prompt_index) * 1211 sizeof *c->prompt_buffer); 1212 utf8_copy(&c->prompt_buffer[c->prompt_index], &tmp); 1213 c->prompt_index++; 1214 } 1215 1216 if (c->prompt_flags & PROMPT_SINGLE) { 1217 s = utf8_tocstr(c->prompt_buffer); 1218 if (strlen(s) != 1) 1219 status_prompt_clear(c); 1220 else if (c->prompt_inputcb(c, c->prompt_data, s, 1) == 0) 1221 status_prompt_clear(c); 1222 free(s); 1223 } 1224 1225 changed: 1226 c->flags |= CLIENT_REDRAWSTATUS; 1227 if (c->prompt_flags & PROMPT_INCREMENTAL) { 1228 s = utf8_tocstr(c->prompt_buffer); 1229 xasprintf(&cp, "%c%s", prefix, s); 1230 c->prompt_inputcb(c, c->prompt_data, cp, 0); 1231 free(cp); 1232 free(s); 1233 } 1234 return (0); 1235 } 1236 1237 /* Get previous line from the history. */ 1238 static const char * 1239 status_prompt_up_history(u_int *idx) 1240 { 1241 /* 1242 * History runs from 0 to size - 1. Index is from 0 to size. Zero is 1243 * empty. 1244 */ 1245 1246 if (status_prompt_hsize == 0 || *idx == status_prompt_hsize) 1247 return (NULL); 1248 (*idx)++; 1249 return (status_prompt_hlist[status_prompt_hsize - *idx]); 1250 } 1251 1252 /* Get next line from the history. */ 1253 static const char * 1254 status_prompt_down_history(u_int *idx) 1255 { 1256 if (status_prompt_hsize == 0 || *idx == 0) 1257 return (""); 1258 (*idx)--; 1259 if (*idx == 0) 1260 return (""); 1261 return (status_prompt_hlist[status_prompt_hsize - *idx]); 1262 } 1263 1264 /* Add line to the history. */ 1265 static void 1266 status_prompt_add_history(const char *line) 1267 { 1268 size_t size; 1269 1270 if (status_prompt_hsize > 0 && 1271 strcmp(status_prompt_hlist[status_prompt_hsize - 1], line) == 0) 1272 return; 1273 1274 if (status_prompt_hsize == PROMPT_HISTORY) { 1275 free(status_prompt_hlist[0]); 1276 1277 size = (PROMPT_HISTORY - 1) * sizeof *status_prompt_hlist; 1278 memmove(&status_prompt_hlist[0], &status_prompt_hlist[1], size); 1279 1280 status_prompt_hlist[status_prompt_hsize - 1] = xstrdup(line); 1281 return; 1282 } 1283 1284 status_prompt_hlist = xreallocarray(status_prompt_hlist, 1285 status_prompt_hsize + 1, sizeof *status_prompt_hlist); 1286 status_prompt_hlist[status_prompt_hsize++] = xstrdup(line); 1287 } 1288 1289 /* Build completion list. */ 1290 char ** 1291 status_prompt_complete_list(u_int *size, const char *s) 1292 { 1293 char **list = NULL; 1294 const char **layout, *value, *cp; 1295 const struct cmd_entry **cmdent; 1296 const struct options_table_entry *oe; 1297 u_int idx; 1298 size_t slen = strlen(s), valuelen; 1299 struct options_entry *o; 1300 struct options_array_item *a; 1301 const char *layouts[] = { 1302 "even-horizontal", "even-vertical", "main-horizontal", 1303 "main-vertical", "tiled", NULL 1304 }; 1305 1306 *size = 0; 1307 for (cmdent = cmd_table; *cmdent != NULL; cmdent++) { 1308 if (strncmp((*cmdent)->name, s, slen) == 0) { 1309 list = xreallocarray(list, (*size) + 1, sizeof *list); 1310 list[(*size)++] = xstrdup((*cmdent)->name); 1311 } 1312 } 1313 for (oe = options_table; oe->name != NULL; oe++) { 1314 if (strncmp(oe->name, s, slen) == 0) { 1315 list = xreallocarray(list, (*size) + 1, sizeof *list); 1316 list[(*size)++] = xstrdup(oe->name); 1317 } 1318 } 1319 for (layout = layouts; *layout != NULL; layout++) { 1320 if (strncmp(*layout, s, slen) == 0) { 1321 list = xreallocarray(list, (*size) + 1, sizeof *list); 1322 list[(*size)++] = xstrdup(*layout); 1323 } 1324 } 1325 o = options_get_only(global_options, "command-alias"); 1326 if (o != NULL) { 1327 a = options_array_first(o); 1328 while (a != NULL) { 1329 value = options_array_item_value(a)->string; 1330 if ((cp = strchr(value, '=')) == NULL) 1331 goto next; 1332 valuelen = cp - value; 1333 if (slen > valuelen || strncmp(value, s, slen) != 0) 1334 goto next; 1335 1336 list = xreallocarray(list, (*size) + 1, sizeof *list); 1337 list[(*size)++] = xstrndup(value, valuelen); 1338 1339 next: 1340 a = options_array_next(a); 1341 } 1342 } 1343 for (idx = 0; idx < (*size); idx++) 1344 log_debug("complete %u: %s", idx, list[idx]); 1345 return (list); 1346 } 1347 1348 /* Find longest prefix. */ 1349 static char * 1350 status_prompt_complete_prefix(char **list, u_int size) 1351 { 1352 char *out; 1353 u_int i; 1354 size_t j; 1355 1356 out = xstrdup(list[0]); 1357 for (i = 1; i < size; i++) { 1358 j = strlen(list[i]); 1359 if (j > strlen(out)) 1360 j = strlen(out); 1361 for (; j > 0; j--) { 1362 if (out[j - 1] != list[i][j - 1]) 1363 out[j - 1] = '\0'; 1364 } 1365 } 1366 return (out); 1367 } 1368 1369 /* Complete word. */ 1370 static char * 1371 status_prompt_complete(struct session *session, const char *s) 1372 { 1373 char **list = NULL; 1374 const char *colon; 1375 u_int size = 0, i; 1376 struct session *s_loop; 1377 struct winlink *wl; 1378 struct window *w; 1379 char *copy, *out, *tmp; 1380 1381 if (*s == '\0') 1382 return (NULL); 1383 out = NULL; 1384 1385 if (strncmp(s, "-t", 2) != 0 && strncmp(s, "-s", 2) != 0) { 1386 list = status_prompt_complete_list(&size, s); 1387 if (size == 0) 1388 out = NULL; 1389 else if (size == 1) 1390 xasprintf(&out, "%s ", list[0]); 1391 else 1392 out = status_prompt_complete_prefix(list, size); 1393 for (i = 0; i < size; i++) 1394 free(list[i]); 1395 free(list); 1396 return (out); 1397 } 1398 copy = xstrdup(s); 1399 1400 colon = ":"; 1401 if (copy[strlen(copy) - 1] == ':') 1402 copy[strlen(copy) - 1] = '\0'; 1403 else 1404 colon = ""; 1405 s = copy + 2; 1406 1407 RB_FOREACH(s_loop, sessions, &sessions) { 1408 if (strncmp(s_loop->name, s, strlen(s)) == 0) { 1409 list = xreallocarray(list, size + 2, sizeof *list); 1410 list[size++] = s_loop->name; 1411 } 1412 } 1413 if (size == 1) { 1414 out = xstrdup(list[0]); 1415 if (session_find(list[0]) != NULL) 1416 colon = ":"; 1417 } else if (size != 0) 1418 out = status_prompt_complete_prefix(list, size); 1419 if (out != NULL) { 1420 xasprintf(&tmp, "-%c%s%s", copy[1], out, colon); 1421 free(out); 1422 out = tmp; 1423 goto found; 1424 } 1425 1426 colon = ""; 1427 if (*s == ':') { 1428 RB_FOREACH(wl, winlinks, &session->windows) { 1429 xasprintf(&tmp, ":%s", wl->window->name); 1430 if (strncmp(tmp, s, strlen(s)) == 0){ 1431 list = xreallocarray(list, size + 1, 1432 sizeof *list); 1433 list[size++] = tmp; 1434 continue; 1435 } 1436 free(tmp); 1437 1438 xasprintf(&tmp, ":%d", wl->idx); 1439 if (strncmp(tmp, s, strlen(s)) == 0) { 1440 list = xreallocarray(list, size + 1, 1441 sizeof *list); 1442 list[size++] = tmp; 1443 continue; 1444 } 1445 free(tmp); 1446 } 1447 } else { 1448 RB_FOREACH(s_loop, sessions, &sessions) { 1449 RB_FOREACH(wl, winlinks, &s_loop->windows) { 1450 w = wl->window; 1451 1452 xasprintf(&tmp, "%s:%s", s_loop->name, w->name); 1453 if (strncmp(tmp, s, strlen(s)) == 0) { 1454 list = xreallocarray(list, size + 1, 1455 sizeof *list); 1456 list[size++] = tmp; 1457 continue; 1458 } 1459 free(tmp); 1460 1461 xasprintf(&tmp, "%s:%d", s_loop->name, wl->idx); 1462 if (strncmp(tmp, s, strlen(s)) == 0) { 1463 list = xreallocarray(list, size + 1, 1464 sizeof *list); 1465 list[size++] = tmp; 1466 continue; 1467 } 1468 free(tmp); 1469 } 1470 } 1471 } 1472 if (size == 1) { 1473 out = xstrdup(list[0]); 1474 colon = " "; 1475 } else if (size != 0) 1476 out = status_prompt_complete_prefix(list, size); 1477 if (out != NULL) { 1478 xasprintf(&tmp, "-%c%s%s", copy[1], out, colon); 1479 out = tmp; 1480 } 1481 1482 for (i = 0; i < size; i++) 1483 free((void *)list[i]); 1484 1485 found: 1486 free(copy); 1487 free(list); 1488 return (out); 1489 } 1490