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