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